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
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-17 14:59:07 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-17 14:59:07 +0300
commit8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca (patch)
tree544930fb309b30317ae9797a9683768705d664c4 /spec
parent4b1de649d0168371549608993deac953eb692019 (diff)
Add latest changes from gitlab-org/gitlab@13-7-stable-eev13.7.0-rc42
Diffstat (limited to 'spec')
-rw-r--r--spec/config/object_store_settings_spec.rb29
-rw-r--r--spec/config/settings_spec.rb16
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb38
-rw-r--r--spec/controllers/admin/clusters/applications_controller_spec.rb6
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb2
-rw-r--r--spec/controllers/admin/integrations_controller_spec.rb24
-rw-r--r--spec/controllers/admin/users_controller_spec.rb51
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb12
-rw-r--r--spec/controllers/dashboard_controller_spec.rb10
-rw-r--r--spec/controllers/groups/boards_controller_spec.rb16
-rw-r--r--spec/controllers/groups/clusters/applications_controller_spec.rb6
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb2
-rw-r--r--spec/controllers/groups/dependency_proxy_auth_controller_spec.rb79
-rw-r--r--spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb95
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb2
-rw-r--r--spec/controllers/groups/releases_controller_spec.rb2
-rw-r--r--spec/controllers/groups/settings/integrations_controller_spec.rb62
-rw-r--r--spec/controllers/groups_controller_spec.rb13
-rw-r--r--spec/controllers/health_check_controller_spec.rb20
-rw-r--r--spec/controllers/help_controller_spec.rb5
-rw-r--r--spec/controllers/import/bulk_imports_controller_spec.rb23
-rw-r--r--spec/controllers/import/github_controller_spec.rb74
-rw-r--r--spec/controllers/import/google_code_controller_spec.rb65
-rw-r--r--spec/controllers/invites_controller_spec.rb39
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb4
-rw-r--r--spec/controllers/profiles/gpg_keys_controller_spec.rb19
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb104
-rw-r--r--spec/controllers/projects/alerting/notifications_controller_spec.rb2
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb16
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb42
-rw-r--r--spec/controllers/projects/ci/lints_controller_spec.rb2
-rw-r--r--spec/controllers/projects/clusters/applications_controller_spec.rb6
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb2
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb4
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb36
-rw-r--r--spec/controllers/projects/feature_flags_controller_spec.rb101
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb56
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb164
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb115
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb12
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb2
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb41
-rw-r--r--spec/controllers/projects/prometheus/alerts_controller_spec.rb2
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb19
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb4
-rw-r--r--spec/controllers/projects/runners_controller_spec.rb88
-rw-r--r--spec/controllers/projects/static_site_editor_controller_spec.rb15
-rw-r--r--spec/controllers/projects/terraform_controller_spec.rb12
-rw-r--r--spec/controllers/projects_controller_spec.rb128
-rw-r--r--spec/controllers/registrations/experience_levels_controller_spec.rb10
-rw-r--r--spec/controllers/repositories/git_http_controller_spec.rb196
-rw-r--r--spec/controllers/repositories/lfs_storage_controller_spec.rb3
-rw-r--r--spec/controllers/root_controller_spec.rb2
-rw-r--r--spec/controllers/snippets_controller_spec.rb7
-rw-r--r--spec/controllers/users_controller_spec.rb332
-rw-r--r--spec/db/production/settings_spec.rb2
-rw-r--r--spec/db/schema_spec.rb5
-rw-r--r--spec/deprecation_toolkit_env.rb18
-rw-r--r--spec/experiments/application_experiment/cache_spec.rb66
-rw-r--r--spec/experiments/application_experiment_spec.rb127
-rw-r--r--spec/factories/alert_management/http_integrations.rb4
-rw-r--r--spec/factories/analytics/devops_adoption/segment_selections.rb18
-rw-r--r--spec/factories/analytics/devops_adoption/segments.rb7
-rw-r--r--spec/factories/bulk_import/failures.rb14
-rw-r--r--spec/factories/ci/builds.rb6
-rw-r--r--spec/factories/ci/job_artifacts.rb22
-rw-r--r--spec/factories/ci/pipelines.rb22
-rw-r--r--spec/factories/dependency_proxy.rb7
-rw-r--r--spec/factories/experiment_subjects.rb9
-rw-r--r--spec/factories/experiment_users.rb10
-rw-r--r--spec/factories/exported_protected_branches.rb5
-rw-r--r--spec/factories/gitlab/database/postgres_index.rb19
-rw-r--r--spec/factories/gitlab/database/postgres_index_bloat_estimate.rb10
-rw-r--r--spec/factories/gitlab/database/reindexing/reindex_action.rb14
-rw-r--r--spec/factories/gpg_keys.rb5
-rw-r--r--spec/factories/issues.rb4
-rw-r--r--spec/factories/merge_requests.rb26
-rw-r--r--spec/factories/namespace_onboarding_actions.rb8
-rw-r--r--spec/factories/packages.rb7
-rw-r--r--spec/factories/packages/package_file.rb8
-rw-r--r--spec/factories/personal_access_tokens.rb4
-rw-r--r--spec/factories/project_repository_storage_moves.rb2
-rw-r--r--spec/factories/project_settings.rb7
-rw-r--r--spec/factories/projects.rb17
-rw-r--r--spec/factories/sent_notifications.rb2
-rw-r--r--spec/factories/sequences.rb4
-rw-r--r--spec/factories/services.rb6
-rw-r--r--spec/factories/snippet_repository_storage_moves.rb29
-rw-r--r--spec/factories/terraform/state.rb8
-rw-r--r--spec/factories/usage_data.rb7
-rw-r--r--spec/factories/users.rb8
-rw-r--r--spec/factories_spec.rb17
-rw-r--r--spec/features/abuse_report_spec.rb2
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb4
-rw-r--r--spec/features/admin/admin_appearance_spec.rb40
-rw-r--r--spec/features/admin/admin_broadcast_messages_spec.rb10
-rw-r--r--spec/features/admin/admin_browse_spam_logs_spec.rb6
-rw-r--r--spec/features/admin/admin_builds_spec.rb4
-rw-r--r--spec/features/admin/admin_cohorts_spec.rb4
-rw-r--r--spec/features/admin/admin_deploy_keys_spec.rb4
-rw-r--r--spec/features/admin/admin_dev_ops_report_spec.rb4
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb1
-rw-r--r--spec/features/admin/admin_disables_two_factor_spec.rb8
-rw-r--r--spec/features/admin/admin_groups_spec.rb19
-rw-r--r--spec/features/admin/admin_health_check_spec.rb1
-rw-r--r--spec/features/admin/admin_hook_logs_spec.rb4
-rw-r--r--spec/features/admin/admin_hooks_spec.rb1
-rw-r--r--spec/features/admin/admin_labels_spec.rb4
-rw-r--r--spec/features/admin/admin_manage_applications_spec.rb4
-rw-r--r--spec/features/admin/admin_mode/login_spec.rb2
-rw-r--r--spec/features/admin/admin_mode/logout_spec.rb2
-rw-r--r--spec/features/admin/admin_mode/workers_spec.rb2
-rw-r--r--spec/features/admin/admin_mode_spec.rb2
-rw-r--r--spec/features/admin/admin_projects_spec.rb1
-rw-r--r--spec/features/admin/admin_requests_profiles_spec.rb4
-rw-r--r--spec/features/admin/admin_runners_spec.rb10
-rw-r--r--spec/features/admin/admin_sees_project_statistics_spec.rb3
-rw-r--r--spec/features/admin/admin_sees_projects_statistics_spec.rb1
-rw-r--r--spec/features/admin/admin_serverless_domains_spec.rb12
-rw-r--r--spec/features/admin/admin_settings_spec.rb58
-rw-r--r--spec/features/admin/admin_system_info_spec.rb4
-rw-r--r--spec/features/admin/admin_users_impersonation_tokens_spec.rb1
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb2
-rw-r--r--spec/features/admin/clusters/applications_spec.rb1
-rw-r--r--spec/features/admin/clusters/eks_spec.rb1
-rw-r--r--spec/features/admin/dashboard_spec.rb4
-rw-r--r--spec/features/admin/services/admin_activates_prometheus_spec.rb1
-rw-r--r--spec/features/admin/services/admin_visits_service_templates_spec.rb1
-rw-r--r--spec/features/admin/users/user_spec.rb372
-rw-r--r--spec/features/admin/users/users_spec.rb (renamed from spec/features/admin/admin_users_spec.rb)480
-rw-r--r--spec/features/alert_management/alert_management_list_spec.rb54
-rw-r--r--spec/features/alerts_settings/user_views_alerts_settings_spec.rb15
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb5
-rw-r--r--spec/features/boards/boards_spec.rb4
-rw-r--r--spec/features/boards/keyboard_shortcut_spec.rb8
-rw-r--r--spec/features/boards/sidebar_spec.rb2
-rw-r--r--spec/features/breadcrumbs_schema_markup_spec.rb15
-rw-r--r--spec/features/clusters/cluster_detail_page_spec.rb4
-rw-r--r--spec/features/commits_spec.rb2
-rw-r--r--spec/features/cycle_analytics_spec.rb14
-rw-r--r--spec/features/dashboard/archived_projects_spec.rb2
-rw-r--r--spec/features/dashboard/group_spec.rb3
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb40
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb4
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb4
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb4
-rw-r--r--spec/features/file_uploads/git_lfs_spec.rb4
-rw-r--r--spec/features/file_uploads/multipart_invalid_uploads_spec.rb2
-rw-r--r--spec/features/file_uploads/nuget_package_spec.rb2
-rw-r--r--spec/features/global_search_spec.rb6
-rw-r--r--spec/features/groups/board_spec.rb2
-rw-r--r--spec/features/groups/container_registry_spec.rb7
-rw-r--r--spec/features/groups/dependency_proxy_spec.rb16
-rw-r--r--spec/features/groups/import_export/connect_instance_spec.rb88
-rw-r--r--spec/features/groups/import_export/import_file_spec.rb8
-rw-r--r--spec/features/groups/members/filter_members_spec.rb26
-rw-r--r--spec/features/groups/members/search_members_spec.rb7
-rw-r--r--spec/features/groups/members/sort_members_spec.rb204
-rw-r--r--spec/features/groups/members/tabs_spec.rb14
-rw-r--r--spec/features/groups/navbar_spec.rb1
-rw-r--r--spec/features/groups/show_spec.rb65
-rw-r--r--spec/features/groups_spec.rb54
-rw-r--r--spec/features/ide/user_sees_editor_info_spec.rb93
-rw-r--r--spec/features/incidents/user_views_incident_spec.rb10
-rw-r--r--spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb2
-rw-r--r--spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb5
-rw-r--r--spec/features/issues/discussion_lock_spec.rb (renamed from spec/features/issuables/discussion_lock_spec.rb)0
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb8
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb112
-rw-r--r--spec/features/issues/issue_header_spec.rb164
-rw-r--r--spec/features/issues/issue_state_spec.rb81
-rw-r--r--spec/features/issues/keyboard_shortcut_spec.rb4
-rw-r--r--spec/features/issues/related_issues_spec.rb (renamed from spec/features/issuables/related_issues_spec.rb)0
-rw-r--r--spec/features/issues/service_desk_spec.rb26
-rw-r--r--spec/features/issues/user_edits_issue_spec.rb1
-rw-r--r--spec/features/issues/user_uses_quick_actions_spec.rb1
-rw-r--r--spec/features/issues/user_views_issue_spec.rb10
-rw-r--r--spec/features/markdown/copy_as_gfm_spec.rb11
-rw-r--r--spec/features/markdown/mermaid_spec.rb8
-rw-r--r--spec/features/merge_request/close_reopen_report_toggle_spec.rb (renamed from spec/features/issuables/close_reopen_report_toggle_spec.rb)118
-rw-r--r--spec/features/merge_request/merge_request_discussion_lock_spec.rb (renamed from spec/features/issuables/merge_request_discussion_lock_spec.rb)0
-rw-r--r--spec/features/merge_request/user_closes_merge_request_spec.rb23
-rw-r--r--spec/features/merge_request/user_closes_reopens_merge_request_state_spec.rb101
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb26
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb10
-rw-r--r--spec/features/merge_request/user_reopens_merge_request_spec.rb28
-rw-r--r--spec/features/merge_request/user_resolves_conflicts_spec.rb5
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb4
-rw-r--r--spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb16
-rw-r--r--spec/features/merge_request/user_squashes_merge_request_spec.rb (renamed from spec/features/merge_requests/user_squashes_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_views_diffs_commit_spec.rb (renamed from spec/features/merge_requests/user_views_diffs_commit_spec.rb)0
-rw-r--r--spec/features/merge_request/user_views_diffs_file_by_file_spec.rb4
-rw-r--r--spec/features/merge_request/user_views_diffs_spec.rb4
-rw-r--r--spec/features/merge_requests/user_sees_empty_state_spec.rb (renamed from spec/features/merge_request/user_sees_empty_state_spec.rb)0
-rw-r--r--spec/features/milestone_spec.rb2
-rw-r--r--spec/features/operations_sidebar_link_spec.rb71
-rw-r--r--spec/features/profiles/account_spec.rb4
-rw-r--r--spec/features/profiles/active_sessions_spec.rb11
-rw-r--r--spec/features/profiles/emails_spec.rb6
-rw-r--r--spec/features/profiles/gpg_keys_spec.rb6
-rw-r--r--spec/features/profiles/keys_spec.rb2
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb26
-rw-r--r--spec/features/profiles/user_changes_notified_of_own_activity_spec.rb4
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb82
-rw-r--r--spec/features/projects/active_tabs_spec.rb8
-rw-r--r--spec/features/projects/blobs/balsamiq_spec.rb17
-rw-r--r--spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb11
-rw-r--r--spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb2
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb2
-rw-r--r--spec/features/projects/clusters_spec.rb2
-rw-r--r--spec/features/projects/container_registry_spec.rb18
-rw-r--r--spec/features/projects/diffs/diff_show_spec.rb4
-rw-r--r--spec/features/projects/environments/environment_metrics_spec.rb2
-rw-r--r--spec/features/projects/environments/environments_spec.rb55
-rw-r--r--spec/features/projects/feature_flags/user_creates_feature_flag_spec.rb112
-rw-r--r--spec/features/projects/features_visibility_spec.rb3
-rw-r--r--spec/features/projects/gfm_autocomplete_load_spec.rb2
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb77
-rw-r--r--spec/features/projects/jobs/permissions_spec.rb162
-rw-r--r--spec/features/projects/jobs_spec.rb133
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb4
-rw-r--r--spec/features/projects/new_project_spec.rb54
-rw-r--r--spec/features/projects/settings/operations_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/registry_settings_spec.rb25
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb4
-rw-r--r--spec/features/projects/settings/service_desk_setting_spec.rb55
-rw-r--r--spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb2
-rw-r--r--spec/features/projects/show/developer_views_empty_project_instructions_spec.rb20
-rw-r--r--spec/features/projects/show/no_password_spec.rb10
-rw-r--r--spec/features/projects/show/schema_markup_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_git_instructions_spec.rb6
-rw-r--r--spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb4
-rw-r--r--spec/features/projects/terraform_spec.rb94
-rw-r--r--spec/features/projects/user_creates_project_spec.rb3
-rw-r--r--spec/features/projects/user_sorts_projects_spec.rb82
-rw-r--r--spec/features/projects/user_views_empty_project_spec.rb26
-rw-r--r--spec/features/projects/wiki/user_git_access_wiki_page_spec.rb21
-rw-r--r--spec/features/projects/wikis_spec.rb1
-rw-r--r--spec/features/projects_spec.rb14
-rw-r--r--spec/features/protected_branches_spec.rb1
-rw-r--r--spec/features/protected_tags_spec.rb2
-rw-r--r--spec/features/registrations/experience_level_spec.rb10
-rw-r--r--spec/features/runners_spec.rb28
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb9
-rw-r--r--spec/features/search/user_searches_for_issues_spec.rb9
-rw-r--r--spec/features/search/user_searches_for_merge_requests_spec.rb9
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb9
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb9
-rw-r--r--spec/features/search/user_uses_search_filters_spec.rb26
-rw-r--r--spec/features/security/admin_access_spec.rb27
-rw-r--r--spec/features/security/project/internal_access_spec.rb48
-rw-r--r--spec/features/security/project/private_access_spec.rb93
-rw-r--r--spec/features/security/project/public_access_spec.rb48
-rw-r--r--spec/features/security/project/snippet/internal_access_spec.rb9
-rw-r--r--spec/features/security/project/snippet/private_access_spec.rb12
-rw-r--r--spec/features/security/project/snippet/public_access_spec.rb9
-rw-r--r--spec/features/snippets/private_snippets_spec.rb2
-rw-r--r--spec/features/snippets/public_snippets_spec.rb4
-rw-r--r--spec/features/snippets/search_snippets_spec.rb2
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb2
-rw-r--r--spec/features/snippets/user_snippets_spec.rb8
-rw-r--r--spec/features/usage_stats_consent_spec.rb1
-rw-r--r--spec/features/users/active_sessions_spec.rb8
-rw-r--r--spec/features/users/login_spec.rb65
-rw-r--r--spec/features/users/show_spec.rb43
-rw-r--r--spec/finders/alert_management/alerts_finder_spec.rb34
-rw-r--r--spec/finders/ci/daily_build_group_report_results_finder_spec.rb87
-rw-r--r--spec/finders/ci/pipeline_schedules_finder_spec.rb2
-rw-r--r--spec/finders/ci/pipelines_finder_spec.rb17
-rw-r--r--spec/finders/ci/pipelines_for_merge_request_finder_spec.rb18
-rw-r--r--spec/finders/feature_flags_finder_spec.rb12
-rw-r--r--spec/finders/fork_projects_finder_spec.rb2
-rw-r--r--spec/finders/group_descendants_finder_spec.rb6
-rw-r--r--spec/finders/group_members_finder_spec.rb44
-rw-r--r--spec/finders/issues_finder_spec.rb4
-rw-r--r--spec/finders/members_finder_spec.rb4
-rw-r--r--spec/finders/merge_requests_finder_spec.rb118
-rw-r--r--spec/finders/releases/evidence_pipeline_finder_spec.rb44
-rw-r--r--spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json30
-rw-r--r--spec/fixtures/api/schemas/entities/codequality_degradation.json24
-rw-r--r--spec/fixtures/api/schemas/entities/codequality_reports_comparer.json43
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json1
-rw-r--r--spec/fixtures/api/schemas/entities/note_user_entity.json3
-rw-r--r--spec/fixtures/api/schemas/graphql/container_repository.json5
-rw-r--r--spec/fixtures/api/schemas/list.json2
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/issue_link.json4
-rw-r--r--spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gzbin0 -> 530 bytes
-rw-r--r--spec/fixtures/codequality/codeclimate.json76
-rw-r--r--spec/fixtures/codequality/codeclimate_without_errors.json1
-rw-r--r--spec/fixtures/codequality/codequality.json1
-rw-r--r--spec/fixtures/codequality/codequality.json.gzbin101478 -> 0 bytes
-rw-r--r--spec/fixtures/csv_empty.csv0
-rw-r--r--spec/fixtures/csv_uppercase.CSV4
-rw-r--r--spec/fixtures/dependency_proxy/manifest38
-rw-r--r--spec/fixtures/lib/gitlab/import_export/designs/project.json4
-rw-r--r--spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/4351.json1
-rw-r--r--spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/4352.json1
-rw-r--r--spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/_all.ndjson2
-rw-r--r--spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree/project.json1
-rw-r--r--spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree/project/labels.ndjson1
-rw-r--r--spec/fixtures/lib/gitlab/performance_bar/peek_data.json104
-rw-r--r--spec/fixtures/valid.po1
-rw-r--r--spec/fixtures/whats_new/20201225_01_01.yml5
-rw-r--r--spec/fixtures/whats_new/20201225_01_02.yml5
-rw-r--r--spec/fixtures/whats_new/20201225_01_05.yml11
-rw-r--r--spec/frontend/.eslintrc.yml3
-rw-r--r--spec/frontend/__mocks__/@toast-ui/vue-editor/index.js17
-rw-r--r--spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap8
-rw-r--r--spec/frontend/admin/users/components/app_spec.js37
-rw-r--r--spec/frontend/admin/users/components/users_table_spec.js61
-rw-r--r--spec/frontend/admin/users/index_spec.js35
-rw-r--r--spec/frontend/admin/users/mock_data.js29
-rw-r--r--spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js2
-rw-r--r--spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js4
-rw-r--r--spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_old_spec.js.snap47
-rw-r--r--spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_spec.js.snap (renamed from spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_new_spec.js.snap)8
-rw-r--r--spec/frontend/alerts_settings/alerts_integrations_list_spec.js3
-rw-r--r--spec/frontend/alerts_settings/alerts_settings_form_old_spec.js204
-rw-r--r--spec/frontend/alerts_settings/alerts_settings_form_spec.js (renamed from spec/frontend/alerts_settings/alerts_settings_form_new_spec.js)20
-rw-r--r--spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js75
-rw-r--r--spec/frontend/api_spec.js40
-rw-r--r--spec/frontend/authentication/two_factor_auth/components/recovery_codes_spec.js242
-rw-r--r--spec/frontend/authentication/two_factor_auth/index_spec.js80
-rw-r--r--spec/frontend/authentication/two_factor_auth/mock_data.js31
-rw-r--r--spec/frontend/blob/components/mock_data.js2
-rw-r--r--spec/frontend/blob/viewer/index_spec.js2
-rw-r--r--spec/frontend/blob_edit/edit_blob_spec.js41
-rw-r--r--spec/frontend/boards/board_list_new_spec.js106
-rw-r--r--spec/frontend/boards/boards_store_spec.js149
-rw-r--r--spec/frontend/boards/components/board_assignee_dropdown_spec.js91
-rw-r--r--spec/frontend/boards/components/board_column_new_spec.js11
-rw-r--r--spec/frontend/boards/components/board_content_spec.js60
-rw-r--r--spec/frontend/boards/components/board_form_spec.js274
-rw-r--r--spec/frontend/boards/components/board_list_header_new_spec.js44
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js2
-rw-r--r--spec/frontend/boards/components/board_new_issue_new_spec.js4
-rw-r--r--spec/frontend/boards/components/boards_selector_spec.js12
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js152
-rw-r--r--spec/frontend/boards/list_spec.js1
-rw-r--r--spec/frontend/boards/mock_data.js69
-rw-r--r--spec/frontend/boards/stores/actions_spec.js235
-rw-r--r--spec/frontend/boards/stores/getters_spec.js76
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js62
-rw-r--r--spec/frontend/branches/ajax_loading_spinner_spec.js2
-rw-r--r--spec/frontend/ci_lint/components/ci_lint_spec.js4
-rw-r--r--spec/frontend/ci_lint/graphql/resolvers_spec.js38
-rw-r--r--spec/frontend/ci_lint/mock_data.js84
-rw-r--r--spec/frontend/close_reopen_report_toggle_spec.js283
-rw-r--r--spec/frontend/clusters/components/__snapshots__/applications_spec.js.snap4
-rw-r--r--spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap2
-rw-r--r--spec/frontend/clusters/stores/clusters_store_spec.js3
-rw-r--r--spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap2
-rw-r--r--spec/frontend/create_cluster/eks_cluster/store/actions_spec.js28
-rw-r--r--spec/frontend/cycle_analytics/banner_spec.js2
-rw-r--r--spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap8
-rw-r--r--spec/frontend/design_management/pages/design/index_spec.js74
-rw-r--r--spec/frontend/design_management/pages/index_spec.js15
-rw-r--r--spec/frontend/diffs/components/app_spec.js198
-rw-r--r--spec/frontend/diffs/components/commit_item_spec.js2
-rw-r--r--spec/frontend/diffs/components/compare_dropdown_layout_spec.js8
-rw-r--r--spec/frontend/diffs/components/compare_versions_spec.js4
-rw-r--r--spec/frontend/diffs/components/diff_content_spec.js16
-rw-r--r--spec/frontend/diffs/components/diff_expansion_cell_spec.js5
-rw-r--r--spec/frontend/diffs/components/diff_file_row_spec.js38
-rw-r--r--spec/frontend/diffs/components/diff_row_spec.js6
-rw-r--r--spec/frontend/diffs/components/image_diff_overlay_spec.js14
-rw-r--r--spec/frontend/diffs/components/settings_dropdown_spec.js95
-rw-r--r--spec/frontend/diffs/diff_file_spec.js60
-rw-r--r--spec/frontend/diffs/store/actions_spec.js48
-rw-r--r--spec/frontend/diffs/store/mutations_spec.js334
-rw-r--r--spec/frontend/diffs/store/utils_spec.js261
-rw-r--r--spec/frontend/diffs/utils/diff_file_spec.js126
-rw-r--r--spec/frontend/diffs/utils/preferences_spec.js40
-rw-r--r--spec/frontend/editor/editor_lite_extension_base_spec.js44
-rw-r--r--spec/frontend/editor/editor_lite_spec.js159
-rw-r--r--spec/frontend/editor/editor_markdown_ext_spec.js4
-rw-r--r--spec/frontend/environments/environment_actions_spec.js131
-rw-r--r--spec/frontend/environments/environment_item_spec.js76
-rw-r--r--spec/frontend/environments/mock_data.js97
-rw-r--r--spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js17
-rw-r--r--spec/frontend/feature_flags/components/edit_feature_flag_spec.js34
-rw-r--r--spec/frontend/feature_flags/components/feature_flags_tab_spec.js5
-rw-r--r--spec/frontend/feature_flags/components/new_feature_flag_spec.js45
-rw-r--r--spec/frontend/filtered_search/filtered_search_manager_spec.js2
-rw-r--r--spec/frontend/fixtures/abuse_reports.rb2
-rw-r--r--spec/frontend/fixtures/admin_users.rb2
-rw-r--r--spec/frontend/fixtures/application_settings.rb2
-rw-r--r--spec/frontend/fixtures/autocomplete_sources.rb5
-rw-r--r--spec/frontend/fixtures/blob.rb4
-rw-r--r--spec/frontend/fixtures/boards.rb30
-rw-r--r--spec/frontend/fixtures/branches.rb6
-rw-r--r--spec/frontend/fixtures/clusters.rb4
-rw-r--r--spec/frontend/fixtures/commit.rb4
-rw-r--r--spec/frontend/fixtures/deploy_keys.rb4
-rw-r--r--spec/frontend/fixtures/emojis.rb17
-rw-r--r--spec/frontend/fixtures/freeze_period.rb10
-rw-r--r--spec/frontend/fixtures/groups.rb10
-rw-r--r--spec/frontend/fixtures/issues.rb18
-rw-r--r--spec/frontend/fixtures/jobs.rb15
-rw-r--r--spec/frontend/fixtures/labels.rb23
-rw-r--r--spec/frontend/fixtures/merge_requests.rb38
-rw-r--r--spec/frontend/fixtures/merge_requests_diffs.rb16
-rw-r--r--spec/frontend/fixtures/pipeline_schedules.rb8
-rw-r--r--spec/frontend/fixtures/pipelines.rb3
-rw-r--r--spec/frontend/fixtures/projects.rb15
-rw-r--r--spec/frontend/fixtures/prometheus_service.rb4
-rw-r--r--spec/frontend/fixtures/raw.rb20
-rw-r--r--spec/frontend/fixtures/search.rb10
-rw-r--r--spec/frontend/fixtures/services.rb4
-rw-r--r--spec/frontend/fixtures/snippet.rb8
-rw-r--r--spec/frontend/fixtures/static/balsamiq_viewer.html1
-rw-r--r--spec/frontend/fixtures/static/create_item_dropdown.html51
-rw-r--r--spec/frontend/fixtures/static/deprecated_jquery_dropdown.html2
-rw-r--r--spec/frontend/fixtures/static/environments/table.html15
-rw-r--r--spec/frontend/fixtures/static/issuable_filter.html9
-rw-r--r--spec/frontend/fixtures/static/issue_with_mermaid_graph.html82
-rw-r--r--spec/frontend/fixtures/static/merge_requests_show.html15
-rw-r--r--spec/frontend/fixtures/static/mini_dropdown_graph.html2
-rw-r--r--spec/frontend/fixtures/static/pipelines.html3
-rw-r--r--spec/frontend/fixtures/static/project_select_combo_button.html14
-rw-r--r--spec/frontend/fixtures/static/whats_new_notification.html6
-rw-r--r--spec/frontend/fixtures/tags.rb4
-rw-r--r--spec/frontend/fixtures/todos.rb10
-rw-r--r--spec/frontend/frequent_items/components/app_spec.js3
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_list_item_spec.js27
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_list_spec.js11
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_search_input_spec.js42
-rw-r--r--spec/frontend/frequent_items/mock_data.js1
-rw-r--r--spec/frontend/graphql_shared/utils_spec.js39
-rw-r--r--spec/frontend/groups/components/app_spec.js2
-rw-r--r--spec/frontend/groups/components/group_item_spec.js49
-rw-r--r--spec/frontend/groups/components/visibility_level_dropdown_spec.js73
-rw-r--r--spec/frontend/groups/members/components/app_spec.js30
-rw-r--r--spec/frontend/groups/members/index_spec.js32
-rw-r--r--spec/frontend/groups/members/utils_spec.js2
-rw-r--r--spec/frontend/groups/store/groups_store_spec.js27
-rw-r--r--spec/frontend/groups/store/utils_spec.js44
-rw-r--r--spec/frontend/helpers/stub_component.js12
-rw-r--r--spec/frontend/helpers/vue_test_utils_helper.js15
-rw-r--r--spec/frontend/helpers/vue_test_utils_helper_spec.js46
-rw-r--r--spec/frontend/helpers/vuex_action_helper.js41
-rw-r--r--spec/frontend/helpers/vuex_action_helper_spec.js264
-rw-r--r--spec/frontend/ide/components/ide_spec.js55
-rw-r--r--spec/frontend/ide/components/terminal/session_spec.js11
-rw-r--r--spec/frontend/ide/components/terminal/view_spec.js18
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js16
-rw-r--r--spec/frontend/ide/utils_spec.js24
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_table_row_spec.js112
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_table_spec.js103
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js221
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/fixtures.js51
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js82
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js213
-rw-r--r--spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js (renamed from spec/frontend/import_projects/components/bitbucket_status_table_spec.js)4
-rw-r--r--spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js (renamed from spec/frontend/import_projects/components/import_projects_table_spec.js)10
-rw-r--r--spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js (renamed from spec/frontend/import_projects/components/provider_repo_table_row_spec.js)6
-rw-r--r--spec/frontend/import_entities/import_projects/store/actions_spec.js (renamed from spec/frontend/import_projects/store/actions_spec.js)10
-rw-r--r--spec/frontend/import_entities/import_projects/store/getters_spec.js (renamed from spec/frontend/import_projects/store/getters_spec.js)6
-rw-r--r--spec/frontend/import_entities/import_projects/store/mutations_spec.js (renamed from spec/frontend/import_projects/store/mutations_spec.js)8
-rw-r--r--spec/frontend/import_entities/import_projects/utils_spec.js (renamed from spec/frontend/import_projects/utils_spec.js)8
-rw-r--r--spec/frontend/importer_status_spec.js141
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap9
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap2
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap14
-rw-r--r--spec/frontend/integrations/edit/store/actions_spec.js30
-rw-r--r--spec/frontend/integrations/edit/store/mutations_spec.js16
-rw-r--r--spec/frontend/invite_members/components/members_token_select_spec.js4
-rw-r--r--spec/frontend/issuable/related_issues/components/issue_token_spec.js2
-rw-r--r--spec/frontend/issuable/related_issues/components/related_issues_root_spec.js28
-rw-r--r--spec/frontend/issuable_show/components/issuable_body_spec.js27
-rw-r--r--spec/frontend/issuable_show/components/issuable_edit_form_spec.js71
-rw-r--r--spec/frontend/issuable_show/components/issuable_show_root_spec.js21
-rw-r--r--spec/frontend/issuable_show/mock_data.js2
-rw-r--r--spec/frontend/issue_show/components/header_actions_spec.js27
-rw-r--r--spec/frontend/issue_show/issue_spec.js3
-rw-r--r--spec/frontend/issue_spec.js202
-rw-r--r--spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap2
-rw-r--r--spec/frontend/jobs/components/log/line_spec.js136
-rw-r--r--spec/frontend/jobs/components/unmet_prerequisites_block_spec.js34
-rw-r--r--spec/frontend/jobs/mixins/delayed_job_mixin_spec.js88
-rw-r--r--spec/frontend/jobs/store/actions_spec.js66
-rw-r--r--spec/frontend/jobs/store/mutations_spec.js1
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js5
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js23
-rw-r--r--spec/frontend/maintenance_mode_settings/components/app_spec.js42
-rw-r--r--spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js (renamed from spec/frontend/vue_shared/components/members/action_buttons/access_request_action_buttons_spec.js)8
-rw-r--r--spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js (renamed from spec/frontend/vue_shared/components/members/action_buttons/approve_access_request_button_spec.js)2
-rw-r--r--spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js (renamed from spec/frontend/vue_shared/components/members/action_buttons/invite_action_buttons_spec.js)8
-rw-r--r--spec/frontend/members/components/action_buttons/leave_button_spec.js (renamed from spec/frontend/vue_shared/components/members/action_buttons/leave_button_spec.js)8
-rw-r--r--spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js (renamed from spec/frontend/vue_shared/components/members/action_buttons/remove_group_link_button_spec.js)4
-rw-r--r--spec/frontend/members/components/action_buttons/remove_member_button_spec.js (renamed from spec/frontend/vue_shared/components/members/action_buttons/remove_member_button_spec.js)2
-rw-r--r--spec/frontend/members/components/action_buttons/resend_invite_button_spec.js (renamed from spec/frontend/vue_shared/components/members/action_buttons/resend_invite_button_spec.js)2
-rw-r--r--spec/frontend/members/components/action_buttons/user_action_buttons_spec.js (renamed from spec/frontend/vue_shared/components/members/action_buttons/user_action_buttons_spec.js)8
-rw-r--r--spec/frontend/members/components/avatars/group_avatar_spec.js (renamed from spec/frontend/vue_shared/components/members/avatars/group_avatar_spec.js)4
-rw-r--r--spec/frontend/members/components/avatars/invite_avatar_spec.js (renamed from spec/frontend/vue_shared/components/members/avatars/invite_avatar_spec.js)4
-rw-r--r--spec/frontend/members/components/avatars/user_avatar_spec.js (renamed from spec/frontend/vue_shared/components/members/avatars/user_avatar_spec.js)4
-rw-r--r--spec/frontend/members/components/filter_sort/filter_sort_container_spec.js68
-rw-r--r--spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js176
-rw-r--r--spec/frontend/members/components/filter_sort/sort_dropdown_spec.js162
-rw-r--r--spec/frontend/members/components/modals/leave_modal_spec.js (renamed from spec/frontend/vue_shared/components/members/modals/leave_modal_spec.js)6
-rw-r--r--spec/frontend/members/components/modals/remove_group_link_modal_spec.js (renamed from spec/frontend/vue_shared/components/members/modals/remove_group_link_modal_spec.js)6
-rw-r--r--spec/frontend/members/components/table/created_at_spec.js (renamed from spec/frontend/vue_shared/components/members/table/created_at_spec.js)2
-rw-r--r--spec/frontend/members/components/table/expiration_datepicker_spec.js (renamed from spec/frontend/vue_shared/components/members/table/expiration_datepicker_spec.js)4
-rw-r--r--spec/frontend/members/components/table/expires_at_spec.js (renamed from spec/frontend/vue_shared/components/members/table/expires_at_spec.js)2
-rw-r--r--spec/frontend/members/components/table/member_action_buttons_spec.js (renamed from spec/frontend/vue_shared/components/members/table/member_action_buttons_spec.js)14
-rw-r--r--spec/frontend/members/components/table/member_avatar_spec.js (renamed from spec/frontend/vue_shared/components/members/table/member_avatar_spec.js)12
-rw-r--r--spec/frontend/members/components/table/member_source_spec.js (renamed from spec/frontend/vue_shared/components/members/table/member_source_spec.js)2
-rw-r--r--spec/frontend/members/components/table/members_table_cell_spec.js (renamed from spec/frontend/vue_shared/components/members/table/member_table_cell_spec.js)8
-rw-r--r--spec/frontend/members/components/table/members_table_spec.js (renamed from spec/frontend/vue_shared/components/members/table/members_table_spec.js)20
-rw-r--r--spec/frontend/members/components/table/role_dropdown_spec.js (renamed from spec/frontend/vue_shared/components/members/table/role_dropdown_spec.js)8
-rw-r--r--spec/frontend/members/mock_data.js (renamed from spec/frontend/vue_shared/components/members/mock_data.js)0
-rw-r--r--spec/frontend/members/store/actions_spec.js (renamed from spec/frontend/vuex_shared/modules/members/actions_spec.js)6
-rw-r--r--spec/frontend/members/store/mutations_spec.js (renamed from spec/frontend/vuex_shared/modules/members/mutations_spec.js)6
-rw-r--r--spec/frontend/members/store/utils_spec.js (renamed from spec/frontend/vuex_shared/modules/members/utils_spec.js)4
-rw-r--r--spec/frontend/members/utils_spec.js (renamed from spec/frontend/vue_shared/components/members/utils_spec.js)112
-rw-r--r--spec/frontend/merge_request_spec.js6
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap2
-rw-r--r--spec/frontend/monitoring/components/group_empty_state_spec.js12
-rw-r--r--spec/frontend/notebook/cells/output/html_sanitize_fixtures.js174
-rw-r--r--spec/frontend/notebook/cells/output/html_spec.js15
-rw-r--r--spec/frontend/notebook/lib/highlight_spec.js4
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js443
-rw-r--r--spec/frontend/notes/components/multiline_comment_utils_spec.js13
-rw-r--r--spec/frontend/notes/components/note_header_spec.js37
-rw-r--r--spec/frontend/notes/mixins/discussion_navigation_spec.js5
-rw-r--r--spec/frontend/notes/stores/actions_spec.js68
-rw-r--r--spec/frontend/notes/stores/mutation_spec.js46
-rw-r--r--spec/frontend/packages/details/components/app_spec.js30
-rw-r--r--spec/frontend/packages/details/components/package_files_spec.js131
-rw-r--r--spec/frontend/packages/details/components/package_history_spec.js113
-rw-r--r--spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap61
-rw-r--r--spec/frontend/packages/mock_data.js3
-rw-r--r--spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap34
-rw-r--r--spec/frontend/pages/admin/users/components/user_modal_manager_spec.js14
-rw-r--r--spec/frontend/pages/admin/users/components/user_operation_confirmation_modal_spec.js46
-rw-r--r--spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js2
-rw-r--r--spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap5
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js31
-rw-r--r--spec/frontend/pipeline_editor/components/commit/commit_form_spec.js116
-rw-r--r--spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js (renamed from spec/frontend/ci_lint/components/ci_lint_results_spec.js)4
-rw-r--r--spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js (renamed from spec/frontend/ci_lint/components/ci_lint_warnings_spec.js)2
-rw-r--r--spec/frontend/pipeline_editor/components/text_editor_spec.js17
-rw-r--r--spec/frontend/pipeline_editor/graphql/__snapshots__/resolvers_spec.js.snap (renamed from spec/frontend/ci_lint/graphql/__snapshots__/resolvers_spec.js.snap)2
-rw-r--r--spec/frontend/pipeline_editor/graphql/resolvers_spec.js51
-rw-r--r--spec/frontend/pipeline_editor/mock_data.js97
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_app_spec.js444
-rw-r--r--spec/frontend/pipeline_new/components/pipeline_new_form_spec.js125
-rw-r--r--spec/frontend/pipeline_new/mock_data.js16
-rw-r--r--spec/frontend/pipeline_new/utils/format_refs_spec.js21
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js86
-rw-r--r--spec/frontend/pipelines/graph/graph_component_legacy_spec.js306
-rw-r--r--spec/frontend/pipelines/graph/graph_component_spec.js312
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js124
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js22
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js40
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_spec.js120
-rw-r--r--spec/frontend/pipelines/graph/mock_data.js896
-rw-r--r--spec/frontend/pipelines/graph/mock_data_legacy.js261
-rw-r--r--spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js135
-rw-r--r--spec/frontend/pipelines/graph/stage_column_component_spec.js164
-rw-r--r--spec/frontend/pipelines/pipeline_graph/gitlab_ci_yaml_visualization_spec.js47
-rw-r--r--spec/frontend/pipelines/pipeline_graph/mock_data.js10
-rw-r--r--spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js148
-rw-r--r--spec/frontend/pipelines/pipeline_graph/utils_spec.js181
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js15
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js4
-rw-r--r--spec/frontend/pipelines/test_reports/stores/getters_spec.js33
-rw-r--r--spec/frontend/pipelines/test_reports/stores/mutations_spec.js13
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js24
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_status_token_spec.js16
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js16
-rw-r--r--spec/frontend/pipelines/unwrapping_utils_spec.js151
-rw-r--r--spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap4
-rw-r--r--spec/frontend/projects/pipelines/charts/components/app_legacy_spec.js72
-rw-r--r--spec/frontend/projects/pipelines/charts/components/app_spec.js66
-rw-r--r--spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js2
-rw-r--r--spec/frontend/projects/pipelines/charts/mock_data.js215
-rw-r--r--spec/frontend/projects/settings/components/shared_runners_toggle_spec.js157
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js39
-rw-r--r--spec/frontend/registry/explorer/components/details_page/__snapshots__/tags_loader_spec.js.snap4
-rw-r--r--spec/frontend/registry/explorer/components/details_page/details_header_spec.js48
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js55
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_list_spec.js10
-rw-r--r--spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap5
-rw-r--r--spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap11
-rw-r--r--spec/frontend/registry/explorer/components/list_page/cli_commands_spec.js41
-rw-r--r--spec/frontend/registry/explorer/components/list_page/group_empty_state_spec.js17
-rw-r--r--spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js61
-rw-r--r--spec/frontend/registry/explorer/components/list_page/image_list_spec.js60
-rw-r--r--spec/frontend/registry/explorer/components/list_page/project_empty_state_spec.js31
-rw-r--r--spec/frontend/registry/explorer/components/registry_breadcrumb_spec.js9
-rw-r--r--spec/frontend/registry/explorer/mock_data.js281
-rw-r--r--spec/frontend/registry/explorer/pages/details_spec.js336
-rw-r--r--spec/frontend/registry/explorer/pages/index_spec.js4
-rw-r--r--spec/frontend/registry/explorer/pages/list_spec.js341
-rw-r--r--spec/frontend/registry/explorer/stores/actions_spec.js362
-rw-r--r--spec/frontend/registry/explorer/stores/getters_spec.js41
-rw-r--r--spec/frontend/registry/explorer/stores/mutations_spec.js133
-rw-r--r--spec/frontend/registry/explorer/stubs.js32
-rw-r--r--spec/frontend/registry/explorer/utils_spec.js56
-rw-r--r--spec/frontend/registry/settings/__snapshots__/utils_spec.js.snap (renamed from spec/frontend/registry/shared/__snapshots__/utils_spec.js.snap)8
-rw-r--r--spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap64
-rw-r--r--spec/frontend/registry/settings/components/expiration_dropdown_spec.js83
-rw-r--r--spec/frontend/registry/settings/components/expiration_input_spec.js169
-rw-r--r--spec/frontend/registry/settings/components/expiration_run_text_spec.js62
-rw-r--r--spec/frontend/registry/settings/components/expiration_toggle_spec.js77
-rw-r--r--spec/frontend/registry/settings/components/registry_settings_app_spec.js33
-rw-r--r--spec/frontend/registry/settings/components/settings_form_spec.js195
-rw-r--r--spec/frontend/registry/settings/graphql/cache_updated_spec.js2
-rw-r--r--spec/frontend/registry/settings/mock_data.js24
-rw-r--r--spec/frontend/registry/settings/utils_spec.js (renamed from spec/frontend/registry/shared/utils_spec.js)7
-rw-r--r--spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap148
-rw-r--r--spec/frontend/registry/shared/components/expiration_policy_fields_spec.js202
-rw-r--r--spec/frontend/registry/shared/mock_data.js12
-rw-r--r--spec/frontend/registry/shared/stubs.js20
-rw-r--r--spec/frontend/reports/components/grouped_test_reports_app_spec.js4
-rw-r--r--spec/frontend/reports/mock_data/recent_failures_report.json10
-rw-r--r--spec/frontend/reports/store/mutations_spec.js5
-rw-r--r--spec/frontend/reports/store/utils_spec.js21
-rw-r--r--spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap1
-rw-r--r--spec/frontend/search/index_spec.js2
-rw-r--r--spec/frontend/search/mock_data.js23
-rw-r--r--spec/frontend/search/store/actions_spec.js83
-rw-r--r--spec/frontend/search/store/mutations_spec.js28
-rw-r--r--spec/frontend/search/topbar/components/group_filter_spec.js121
-rw-r--r--spec/frontend/search/topbar/components/project_filter_spec.js134
-rw-r--r--spec/frontend/search/topbar/components/searchable_dropdown_spec.js (renamed from spec/frontend/search/group_filter/components/group_filter_spec.js)91
-rw-r--r--spec/frontend/search_spec.js25
-rw-r--r--spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap8
-rw-r--r--spec/frontend/sidebar/__snapshots__/todo_spec.js.snap5
-rw-r--r--spec/frontend/sidebar/issuable_assignees_spec.js12
-rw-r--r--spec/frontend/sidebar/sidebar_labels_spec.js2
-rw-r--r--spec/frontend/static_site_editor/components/edit_area_spec.js13
-rw-r--r--spec/frontend/static_site_editor/pages/home_spec.js9
-rw-r--r--spec/frontend/static_site_editor/services/submit_content_changes_spec.js56
-rw-r--r--spec/frontend/terraform/components/states_table_actions_spec.js189
-rw-r--r--spec/frontend/terraform/components/states_table_spec.js71
-rw-r--r--spec/frontend/test_setup.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_merge_help_spec.js66
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js402
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js68
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js51
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_spec.js5
-rw-r--r--spec/frontend/vue_mr_widget/mock_data.js3
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_how_to_merge_modal_spec.js68
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js18
-rw-r--r--spec/frontend/vue_mr_widget/stores/mr_widget_store_spec.js20
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap191
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap2
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap4
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap4
-rw-r--r--spec/frontend/vue_shared/components/awards_list_spec.js36
-rw-r--r--spec/frontend/vue_shared/components/callout_spec.js63
-rw-r--r--spec/frontend/vue_shared/components/clipboard_button_spec.js42
-rw-r--r--spec/frontend/vue_shared/components/color_picker/color_picker_spec.js140
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js39
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js24
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap47
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js (renamed from spec/frontend/vue_shared/components/gl_mentions_spec.js)10
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js344
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_milestone_spec.js29
-rw-r--r--spec/frontend/vue_shared/components/loading_button_spec.js100
-rw-r--r--spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js72
-rw-r--r--spec/frontend/vue_shared/components/registry/__snapshots__/code_instruction_spec.js.snap1
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap6
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js13
-rw-r--r--spec/frontend/vue_shared/components/security_reports/__snapshots__/security_summary_spec.js.snap144
-rw-r--r--spec/frontend/vue_shared/components/security_reports/help_icon_spec.js68
-rw-r--r--spec/frontend/vue_shared/components/security_reports/security_summary_spec.js38
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js12
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js104
-rw-r--r--spec/frontend/vue_shared/components/toggle_button_spec.js107
-rw-r--r--spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js (renamed from spec/javascripts/vue_shared/components/tooltip_on_truncate_browser_spec.js)87
-rw-r--r--spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js64
-rw-r--r--spec/frontend/vue_shared/security_reports/mock_data.js437
-rw-r--r--spec/frontend/vue_shared/security_reports/security_reports_app_spec.js497
-rw-r--r--spec/frontend/vue_shared/security_reports/store/getters_spec.js182
-rw-r--r--spec/frontend/vue_shared/security_reports/utils_spec.js28
-rw-r--r--spec/frontend/whats_new/components/app_spec.js201
-rw-r--r--spec/frontend/whats_new/store/actions_spec.js17
-rw-r--r--spec/frontend/whats_new/utils/notification_spec.js55
-rw-r--r--spec/frontend_integration/.eslintrc.yml4
-rw-r--r--spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap114
-rw-r--r--spec/frontend_integration/ide/helpers/ide_helper.js (renamed from spec/frontend_integration/ide/ide_helper.js)71
-rw-r--r--spec/frontend_integration/ide/helpers/mock_data.js12
-rw-r--r--spec/frontend_integration/ide/helpers/start.js17
-rw-r--r--spec/frontend_integration/ide/ide_integration_spec.js136
-rw-r--r--spec/frontend_integration/ide/user_opens_file_spec.js89
-rw-r--r--spec/frontend_integration/ide/user_opens_ide_spec.js160
-rw-r--r--spec/frontend_integration/test_helpers/fixtures.js42
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/index.js27
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/routes/repository.js12
-rw-r--r--spec/graphql/features/authorization_spec.rb22
-rw-r--r--spec/graphql/features/feature_flag_spec.rb2
-rw-r--r--spec/graphql/mutations/alert_management/create_alert_issue_spec.rb1
-rw-r--r--spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb2
-rw-r--r--spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb2
-rw-r--r--spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb2
-rw-r--r--spec/graphql/mutations/alert_management/update_alert_status_spec.rb2
-rw-r--r--spec/graphql/mutations/boards/issues/issue_move_list_spec.rb18
-rw-r--r--spec/graphql/mutations/boards/lists/create_spec.rb9
-rw-r--r--spec/graphql/mutations/concerns/mutations/finds_by_gid_spec.rb26
-rw-r--r--spec/graphql/mutations/container_expiration_policies/update_spec.rb2
-rw-r--r--spec/graphql/mutations/container_repositories/destroy_tags_spec.rb92
-rw-r--r--spec/graphql/mutations/discussions/toggle_resolve_spec.rb2
-rw-r--r--spec/graphql/mutations/environments/canary_ingress/update_spec.rb66
-rw-r--r--spec/graphql/mutations/issues/create_spec.rb8
-rw-r--r--spec/graphql/mutations/issues/update_spec.rb2
-rw-r--r--spec/graphql/mutations/labels/create_spec.rb2
-rw-r--r--spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb2
-rw-r--r--spec/graphql/mutations/releases/delete_spec.rb92
-rw-r--r--spec/graphql/mutations/releases/update_spec.rb232
-rw-r--r--spec/graphql/resolvers/alert_management/alert_resolver_spec.rb12
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb8
-rw-r--r--spec/graphql/resolvers/board_list_issues_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/board_lists_resolver_spec.rb12
-rw-r--r--spec/graphql/resolvers/ci/config_resolver_spec.rb56
-rw-r--r--spec/graphql/resolvers/ci/jobs_resolver_spec.rb20
-rw-r--r--spec/graphql/resolvers/commit_pipelines_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb83
-rw-r--r--spec/graphql/resolvers/concerns/looks_ahead_spec.rb3
-rw-r--r--spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb6
-rw-r--r--spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb6
-rw-r--r--spec/graphql/resolvers/design_management/versions_resolver_spec.rb18
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/issue_status_counts_resolver_spec.rb11
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb48
-rw-r--r--spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/merge_requests_resolver_spec.rb22
-rw-r--r--spec/graphql/resolvers/project_merge_requests_resolver_spec.rb26
-rw-r--r--spec/graphql/resolvers/project_pipeline_resolver_spec.rb19
-rw-r--r--spec/graphql/resolvers/project_pipeline_statistics_resolver_spec.rb36
-rw-r--r--spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb73
-rw-r--r--spec/graphql/resolvers/projects/snippets_resolver_spec.rb26
-rw-r--r--spec/graphql/resolvers/releases_resolver_spec.rb21
-rw-r--r--spec/graphql/resolvers/snippets/blobs_resolver_spec.rb26
-rw-r--r--spec/graphql/resolvers/snippets_resolver_spec.rb11
-rw-r--r--spec/graphql/resolvers/todo_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/user_discussions_count_resolver_spec.rb54
-rw-r--r--spec/graphql/resolvers/user_notes_count_resolver_spec.rb93
-rw-r--r--spec/graphql/resolvers/users/snippets_resolver_spec.rb13
-rw-r--r--spec/graphql/types/alert_management/domain_filter_enum_spec.rb11
-rw-r--r--spec/graphql/types/alert_management/prometheus_integration_type_spec.rb5
-rw-r--r--spec/graphql/types/base_field_spec.rb10
-rw-r--r--spec/graphql/types/ci/analytics_type_spec.rb23
-rw-r--r--spec/graphql/types/ci/config/config_type_spec.rb18
-rw-r--r--spec/graphql/types/ci/config/group_type_spec.rb17
-rw-r--r--spec/graphql/types/ci/config/job_type_spec.rb18
-rw-r--r--spec/graphql/types/ci/config/need_type_spec.rb15
-rw-r--r--spec/graphql/types/ci/config/stage_type_spec.rb16
-rw-r--r--spec/graphql/types/ci/job_artifact_file_type_enum_spec.rb11
-rw-r--r--spec/graphql/types/ci/job_artifact_type_spec.rb11
-rw-r--r--spec/graphql/types/ci/job_type_spec.rb1
-rw-r--r--spec/graphql/types/ci/pipeline_type_spec.rb15
-rw-r--r--spec/graphql/types/commit_type_spec.rb2
-rw-r--r--spec/graphql/types/container_repository_details_type_spec.rb2
-rw-r--r--spec/graphql/types/container_repository_type_spec.rb2
-rw-r--r--spec/graphql/types/countable_connection_type_spec.rb2
-rw-r--r--spec/graphql/types/group_member_relation_enum_spec.rb11
-rw-r--r--spec/graphql/types/group_type_spec.rb2
-rw-r--r--spec/graphql/types/merge_request_connection_type_spec.rb11
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb11
-rw-r--r--spec/graphql/types/permission_types/base_permission_type_spec.rb6
-rw-r--r--spec/graphql/types/project_member_relation_enum_spec.rb11
-rw-r--r--spec/graphql/types/project_type_spec.rb34
-rw-r--r--spec/graphql/types/terraform/state_version_type_spec.rb4
-rw-r--r--spec/graphql/types/user_type_spec.rb3
-rw-r--r--spec/helpers/admin/user_actions_helper_spec.rb105
-rw-r--r--spec/helpers/application_helper_spec.rb4
-rw-r--r--spec/helpers/auth_helper_spec.rb16
-rw-r--r--spec/helpers/blob_helper_spec.rb62
-rw-r--r--spec/helpers/button_helper_spec.rb2
-rw-r--r--spec/helpers/calendar_helper_spec.rb9
-rw-r--r--spec/helpers/ci/pipeline_schedules_helper_spec.rb24
-rw-r--r--spec/helpers/ci/runners_helper_spec.rb53
-rw-r--r--spec/helpers/clusters_helper_spec.rb2
-rw-r--r--spec/helpers/defer_script_tag_helper_spec.rb14
-rw-r--r--spec/helpers/diff_helper_spec.rb26
-rw-r--r--spec/helpers/emails_helper_spec.rb2
-rw-r--r--spec/helpers/gitlab_script_tag_helper_spec.rb44
-rw-r--r--spec/helpers/groups/group_members_helper_spec.rb4
-rw-r--r--spec/helpers/icons_helper_spec.rb17
-rw-r--r--spec/helpers/issuables_helper_spec.rb26
-rw-r--r--spec/helpers/markup_helper_spec.rb18
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb20
-rw-r--r--spec/helpers/nav_helper_spec.rb2
-rw-r--r--spec/helpers/notifications_helper_spec.rb17
-rw-r--r--spec/helpers/operations_helper_spec.rb34
-rw-r--r--spec/helpers/projects/alert_management_helper_spec.rb32
-rw-r--r--spec/helpers/projects/terraform_helper_spec.rb27
-rw-r--r--spec/helpers/projects_helper_spec.rb62
-rw-r--r--spec/helpers/rss_helper_spec.rb9
-rw-r--r--spec/helpers/search_helper_spec.rb41
-rw-r--r--spec/helpers/services_helper_spec.rb55
-rw-r--r--spec/helpers/sorting_helper_spec.rb1
-rw-r--r--spec/helpers/storage_helper_spec.rb6
-rw-r--r--spec/helpers/time_zone_helper_spec.rb71
-rw-r--r--spec/helpers/tree_helper_spec.rb18
-rw-r--r--spec/helpers/user_callouts_helper_spec.rb17
-rw-r--r--spec/helpers/users_helper_spec.rb78
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb30
-rw-r--r--spec/helpers/whats_new_helper_spec.rb33
-rw-r--r--spec/initializers/lograge_spec.rb28
-rw-r--r--spec/initializers/secret_token_spec.rb36
-rw-r--r--spec/javascripts/blob/balsamiq/balsamiq_viewer_browser_spec.js59
-rw-r--r--spec/lib/api/entities/merge_request_basic_spec.rb27
-rw-r--r--spec/lib/api/helpers/sse_helpers_spec.rb44
-rw-r--r--spec/lib/api/validations/validators/integer_or_custom_value_spec.rb46
-rw-r--r--spec/lib/atlassian/jira_connect/client_spec.rb165
-rw-r--r--spec/lib/atlassian/jira_connect/serializers/build_entity_spec.rb52
-rw-r--r--spec/lib/backup/files_spec.rb18
-rw-r--r--spec/lib/backup/repositories_spec.rb4
-rw-r--r--spec/lib/banzai/filter/ascii_doc_sanitization_filter_spec.rb58
-rw-r--r--spec/lib/banzai/filter/kroki_filter_spec.rb41
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb6
-rw-r--r--spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb2
-rw-r--r--spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb7
-rw-r--r--spec/lib/bulk_imports/common/transformers/graphql_cleaner_transformer_spec.rb88
-rw-r--r--spec/lib/bulk_imports/common/transformers/hash_key_digger_spec.rb28
-rw-r--r--spec/lib/bulk_imports/common/transformers/prohibited_attributes_transformer_spec.rb72
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb11
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb5
-rw-r--r--spec/lib/bulk_imports/importers/group_importer_spec.rb30
-rw-r--r--spec/lib/bulk_imports/pipeline/runner_spec.rb169
-rw-r--r--spec/lib/bulk_imports/pipeline_spec.rb (renamed from spec/lib/bulk_imports/pipeline/attributes_spec.rb)10
-rw-r--r--spec/lib/feature/definition_spec.rb63
-rw-r--r--spec/lib/feature_spec.rb173
-rw-r--r--spec/lib/gitlab/anonymous_session_spec.rb2
-rw-r--r--spec/lib/gitlab/asciidoc/html5_converter_spec.rb15
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb53
-rw-r--r--spec/lib/gitlab/auth/auth_finders_spec.rb17
-rw-r--r--spec/lib/gitlab/auth/crowd/authentication_spec.rb48
-rw-r--r--spec/lib/gitlab/auth/ldap/user_spec.rb17
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb17
-rw-r--r--spec/lib/gitlab/auth/otp/session_enforcer_spec.rb41
-rw-r--r--spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb24
-rw-r--r--spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb87
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb56
-rw-r--r--spec/lib/gitlab/auth_spec.rb23
-rw-r--r--spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb12
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb10
-rw-r--r--spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb20
-rw-r--r--spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb22
-rw-r--r--spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb48
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb22
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb20
-rw-r--r--spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb48
-rw-r--r--spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb28
-rw-r--r--spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb16
-rw-r--r--spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities_spec.rb44
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/reset_merge_status_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth_spec.rb74
-rw-r--r--spec/lib/gitlab/checks/diff_check_spec.rb21
-rw-r--r--spec/lib/gitlab/checks/push_check_spec.rb21
-rw-r--r--spec/lib/gitlab/checks/snippet_check_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/ansi2json/result_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json/style_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/build/rules_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/config/entry/allow_failure_spec.rb92
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/config/entry/need_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/config/entry/needs_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/services_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/variables_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/mask_secret_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb138
-rw-r--r--spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb749
-rw-r--r--spec/lib/gitlab/ci/parsers_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb109
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb62
-rw-r--r--spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb95
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb66
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb75
-rw-r--r--spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb153
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb308
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_spec.rb136
-rw-r--r--spec/lib/gitlab/ci/reports/reports_comparer_spec.rb97
-rw-r--r--spec/lib/gitlab/ci/templates/npm_spec.rb92
-rw-r--r--spec/lib/gitlab/ci/trace/checksum_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb106
-rw-r--r--spec/lib/gitlab/cleanup/project_uploads_spec.rb18
-rw-r--r--spec/lib/gitlab/config/entry/configurable_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb3
-rw-r--r--spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb4
-rw-r--r--spec/lib/gitlab/cycle_analytics/usage_data_spec.rb95
-rw-r--r--spec/lib/gitlab/danger/base_linter_spec.rb154
-rw-r--r--spec/lib/gitlab/danger/commit_linter_spec.rb135
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb36
-rw-r--r--spec/lib/gitlab/danger/merge_request_linter_spec.rb55
-rw-r--r--spec/lib/gitlab/danger/roulette_spec.rb8
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb16
-rw-r--r--spec/lib/gitlab/database/batch_count_spec.rb23
-rw-r--r--spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb46
-rw-r--r--spec/lib/gitlab/database/postgres_hll/batch_distinct_counter_spec.rb132
-rw-r--r--spec/lib/gitlab/database/postgres_index_bloat_estimate_spec.rb34
-rw-r--r--spec/lib/gitlab/database/postgres_index_spec.rb23
-rw-r--r--spec/lib/gitlab/database/postgresql_adapter/empty_query_ping_spec.rb43
-rw-r--r--spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb32
-rw-r--r--spec/lib/gitlab/database/reindexing/index_selection_spec.rb50
-rw-r--r--spec/lib/gitlab/database/reindexing/reindex_action_spec.rb8
-rw-r--r--spec/lib/gitlab/database/reindexing_spec.rb6
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb4
-rw-r--r--spec/lib/gitlab/deploy_key_access_spec.rb66
-rw-r--r--spec/lib/gitlab/diff/file_collection/commit_spec.rb72
-rw-r--r--spec/lib/gitlab/diff/file_collection/compare_spec.rb39
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb31
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb7
-rw-r--r--spec/lib/gitlab/diff/file_collection_sorter_spec.rb51
-rw-r--r--spec/lib/gitlab/diff/lines_unfolder_spec.rb4
-rw-r--r--spec/lib/gitlab/email/handler/service_desk_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/reply_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/email/smime/certificate_spec.rb14
-rw-r--r--spec/lib/gitlab/encrypted_configuration_spec.rb145
-rw-r--r--spec/lib/gitlab/experimentation/controller_concern_spec.rb216
-rw-r--r--spec/lib/gitlab/experimentation/experiment_spec.rb55
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb152
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb66
-rw-r--r--spec/lib/gitlab/git_access_project_spec.rb17
-rw-r--r--spec/lib/gitlab/git_access_spec.rb117
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb27
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb24
-rw-r--r--spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb69
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb41
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb202
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb1
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests_merged_by_importer_spec.rb47
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests_reviews_importer_spec.rb46
-rw-r--r--spec/lib/gitlab/github_import/parallel_scheduling_spec.rb80
-rw-r--r--spec/lib/gitlab/github_import/representation/pull_request_review_spec.rb72
-rw-r--r--spec/lib/gitlab/github_import/representation/pull_request_spec.rb1
-rw-r--r--spec/lib/gitlab/google_code_import/client_spec.rb38
-rw-r--r--spec/lib/gitlab/google_code_import/importer_spec.rb88
-rw-r--r--spec/lib/gitlab/google_code_import/project_creator_spec.rb32
-rw-r--r--spec/lib/gitlab/graphql/docs/renderer_spec.rb26
-rw-r--r--spec/lib/gitlab/graphql/markdown_field_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/array_connection_spec.rb15
-rw-r--r--spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb8
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb11
-rw-r--r--spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb11
-rw-r--r--spec/lib/gitlab/graphql/timeout_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/group_member_builder_spec.rb60
-rw-r--r--spec/lib/gitlab/i18n/po_linter_spec.rb55
-rw-r--r--spec/lib/gitlab/i18n/translation_entry_spec.rb112
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml10
-rw-r--r--spec/lib/gitlab/import_export/group/tree_restorer_spec.rb25
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb15
-rw-r--r--spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb27
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb74
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml6
-rw-r--r--spec/lib/gitlab/import_export/wiki_restorer_spec.rb47
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb3
-rw-r--r--spec/lib/gitlab/instrumentation_helper_spec.rb9
-rw-r--r--spec/lib/gitlab/kubernetes/deployment_spec.rb190
-rw-r--r--spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb26
-rw-r--r--spec/lib/gitlab/kubernetes/ingress_spec.rb57
-rw-r--r--spec/lib/gitlab/kubernetes/rollout_instances_spec.rb128
-rw-r--r--spec/lib/gitlab/kubernetes/rollout_status_spec.rb271
-rw-r--r--spec/lib/gitlab/metrics/background_transaction_spec.rb56
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb66
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb119
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb8
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb6
-rw-r--r--spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb35
-rw-r--r--spec/lib/gitlab/pagination/offset_header_builder_spec.rb61
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb96
-rw-r--r--spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb64
-rw-r--r--spec/lib/gitlab/performance_bar/stats_spec.rb42
-rw-r--r--spec/lib/gitlab/rack_attack/user_allowlist_spec.rb33
-rw-r--r--spec/lib/gitlab/rack_attack_spec.rb96
-rw-r--r--spec/lib/gitlab/sample_data_template_spec.rb5
-rw-r--r--spec/lib/gitlab/setup_helper/workhorse_spec.rb25
-rw-r--r--spec/lib/gitlab/sidekiq_cluster_spec.rb8
-rw-r--r--spec/lib/gitlab/sidekiq_death_handler_spec.rb50
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb65
-rw-r--r--spec/lib/gitlab/tracking/destinations/product_analytics_spec.rb84
-rw-r--r--spec/lib/gitlab/tracking/destinations/snowplow_spec.rb4
-rw-r--r--spec/lib/gitlab/tracking_spec.rb17
-rw-r--r--spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/base_counter_spec.rb34
-rw-r--r--spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb14
-rw-r--r--spec/lib/gitlab/usage_data_counters/guest_package_event_counter_spec.rb31
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb4
-rw-r--r--spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb10
-rw-r--r--spec/lib/gitlab/usage_data_counters/search_counter_spec.rb8
-rw-r--r--spec/lib/gitlab/usage_data_counters_spec.rb32
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb93
-rw-r--r--spec/lib/gitlab/user_access_spec.rb20
-rw-r--r--spec/lib/gitlab/utils/usage_data_spec.rb30
-rw-r--r--spec/lib/gitlab/uuid_spec.rb52
-rw-r--r--spec/lib/gitlab/webpack/manifest_spec.rb4
-rw-r--r--spec/lib/gitlab_danger_spec.rb4
-rw-r--r--spec/lib/gitlab_spec.rb31
-rw-r--r--spec/lib/microsoft_teams/notifier_spec.rb4
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb47
-rw-r--r--spec/lib/product_analytics/tracker_spec.rb49
-rw-r--r--spec/lib/quality/test_level_spec.rb8
-rw-r--r--spec/mailers/emails/projects_spec.rb6
-rw-r--r--spec/mailers/emails/releases_spec.rb9
-rw-r--r--spec/mailers/notify_spec.rb34
-rw-r--r--spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb16
-rw-r--r--spec/migrations/20200122123016_backfill_project_settings_spec.rb10
-rw-r--r--spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb22
-rw-r--r--spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb18
-rw-r--r--spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb24
-rw-r--r--spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb6
-rw-r--r--spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb10
-rw-r--r--spec/migrations/20200526115436_dedup_mr_metrics_spec.rb12
-rw-r--r--spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb2
-rw-r--r--spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb2
-rw-r--r--spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb12
-rw-r--r--spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb2
-rw-r--r--spec/migrations/backfill_imported_snippet_repositories_spec.rb2
-rw-r--r--spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb4
-rw-r--r--spec/migrations/backfill_snippet_repositories_spec.rb2
-rw-r--r--spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb4
-rw-r--r--spec/migrations/enqueue_reset_merge_status_second_run_spec.rb4
-rw-r--r--spec/migrations/enqueue_reset_merge_status_spec.rb4
-rw-r--r--spec/migrations/ensure_namespace_settings_creation_spec.rb2
-rw-r--r--spec/migrations/ensure_u2f_registrations_migrated_spec.rb41
-rw-r--r--spec/migrations/fill_file_store_lfs_objects_spec.rb6
-rw-r--r--spec/migrations/fill_store_uploads_spec.rb6
-rw-r--r--spec/migrations/fix_null_type_labels_spec.rb12
-rw-r--r--spec/migrations/fix_pool_repository_source_project_id_spec.rb4
-rw-r--r--spec/migrations/fix_projects_without_project_feature_spec.rb8
-rw-r--r--spec/migrations/fix_projects_without_prometheus_services_spec.rb8
-rw-r--r--spec/migrations/fix_wrong_pages_access_level_spec.rb2
-rw-r--r--spec/migrations/insert_project_hooks_plan_limits_spec.rb10
-rw-r--r--spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb2
-rw-r--r--spec/migrations/move_limits_from_plans_spec.rb10
-rw-r--r--spec/migrations/populate_project_statistics_packages_size_spec.rb2
-rw-r--r--spec/migrations/populate_remaining_missing_dismissal_information_for_vulnerabilities_spec.rb31
-rw-r--r--spec/migrations/remove_orphan_service_hooks_spec.rb26
-rw-r--r--spec/migrations/schedule_link_lfs_objects_projects_spec.rb34
-rw-r--r--spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb4
-rw-r--r--spec/migrations/schedule_repopulate_historical_vulnerability_statistics_spec.rb36
-rw-r--r--spec/migrations/schedule_update_existing_users_that_require_two_factor_auth_spec.rb29
-rw-r--r--spec/migrations/seed_repository_storages_weighted_spec.rb2
-rw-r--r--spec/migrations/update_internal_ids_last_value_for_epics_renamed_spec.rb30
-rw-r--r--spec/models/alert_management/alert_spec.rb7
-rw-r--r--spec/models/alert_management/http_integration_spec.rb48
-rw-r--r--spec/models/analytics/devops_adoption/segment_selection_spec.rb69
-rw-r--r--spec/models/application_setting_spec.rb13
-rw-r--r--spec/models/bulk_imports/entity_spec.rb64
-rw-r--r--spec/models/bulk_imports/failure_spec.rb17
-rw-r--r--spec/models/ci/bridge_spec.rb16
-rw-r--r--spec/models/ci/build_dependencies_spec.rb202
-rw-r--r--spec/models/ci/build_spec.rb194
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/fog_spec.rb46
-rw-r--r--spec/models/ci/job_artifact_spec.rb16
-rw-r--r--spec/models/ci/pipeline_spec.rb575
-rw-r--r--spec/models/clusters/agent_spec.rb12
-rw-r--r--spec/models/clusters/applications/helm_spec.rb40
-rw-r--r--spec/models/clusters/cluster_spec.rb6
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb399
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb93
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb12
-rw-r--r--spec/models/concerns/ignorable_columns_spec.rb8
-rw-r--r--spec/models/concerns/mentionable_spec.rb36
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb2
-rw-r--r--spec/models/concerns/routable_spec.rb150
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb4
-rw-r--r--spec/models/container_repository_spec.rb2
-rw-r--r--spec/models/custom_emoji_spec.rb9
-rw-r--r--spec/models/cycle_analytics/code_spec.rb2
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb2
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb2
-rw-r--r--spec/models/cycle_analytics/review_spec.rb2
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb2
-rw-r--r--spec/models/cycle_analytics/test_spec.rb2
-rw-r--r--spec/models/dependency_proxy/manifest_spec.rb52
-rw-r--r--spec/models/dependency_proxy/registry_spec.rb7
-rw-r--r--spec/models/deployment_spec.rb86
-rw-r--r--spec/models/diff_note_spec.rb20
-rw-r--r--spec/models/environment_spec.rb173
-rw-r--r--spec/models/experiment_spec.rb113
-rw-r--r--spec/models/experiment_subject_spec.rb54
-rw-r--r--spec/models/exported_protected_branch_spec.rb21
-rw-r--r--spec/models/group_import_state_spec.rb20
-rw-r--r--spec/models/group_spec.rb69
-rw-r--r--spec/models/identity_spec.rb30
-rw-r--r--spec/models/instance_configuration_spec.rb16
-rw-r--r--spec/models/issue_spec.rb19
-rw-r--r--spec/models/label_priority_spec.rb6
-rw-r--r--spec/models/merge_request_diff_spec.rb212
-rw-r--r--spec/models/merge_request_reviewer_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb221
-rw-r--r--spec/models/namespace_onboarding_action_spec.rb53
-rw-r--r--spec/models/namespace_spec.rb1
-rw-r--r--spec/models/note_spec.rb4
-rw-r--r--spec/models/packages/package_file_spec.rb4
-rw-r--r--spec/models/packages/package_spec.rb73
-rw-r--r--spec/models/pages/lookup_path_spec.rb63
-rw-r--r--spec/models/project_feature_spec.rb2
-rw-r--r--spec/models/project_feature_usage_spec.rb4
-rw-r--r--spec/models/project_repository_storage_move_spec.rb94
-rw-r--r--spec/models/project_services/datadog_service_spec.rb179
-rw-r--r--spec/models/project_services/jenkins_service_spec.rb255
-rw-r--r--spec/models/project_services/jira_service_spec.rb13
-rw-r--r--spec/models/project_spec.rb160
-rw-r--r--spec/models/project_statistics_spec.rb11
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb55
-rw-r--r--spec/models/raw_usage_data_spec.rb24
-rw-r--r--spec/models/release_highlight_spec.rb181
-rw-r--r--spec/models/repository_spec.rb2
-rw-r--r--spec/models/resource_label_event_spec.rb8
-rw-r--r--spec/models/resource_state_event_spec.rb8
-rw-r--r--spec/models/sentry_issue_spec.rb6
-rw-r--r--spec/models/service_spec.rb13
-rw-r--r--spec/models/snippet_repository_storage_move_spec.rb13
-rw-r--r--spec/models/snippet_spec.rb39
-rw-r--r--spec/models/suggestion_spec.rb6
-rw-r--r--spec/models/system_note_metadata_spec.rb12
-rw-r--r--spec/models/terraform/state_spec.rb113
-rw-r--r--spec/models/timelog_spec.rb8
-rw-r--r--spec/models/user_spec.rb276
-rw-r--r--spec/models/wiki_page/meta_spec.rb9
-rw-r--r--spec/models/wiki_page/slug_spec.rb3
-rw-r--r--spec/models/zoom_meeting_spec.rb14
-rw-r--r--spec/policies/global_policy_spec.rb38
-rw-r--r--spec/policies/namespace_policy_spec.rb2
-rw-r--r--spec/policies/project_policy_spec.rb174
-rw-r--r--spec/policies/user_policy_spec.rb12
-rw-r--r--spec/presenters/gitlab/whats_new/item_presenter_spec.rb29
-rw-r--r--spec/presenters/packages/composer/packages_presenter_spec.rb2
-rw-r--r--spec/presenters/project_presenter_spec.rb6
-rw-r--r--spec/presenters/projects/import_export/project_export_presenter_spec.rb8
-rw-r--r--spec/presenters/search_service_presenter_spec.rb34
-rw-r--r--spec/presenters/snippet_presenter_spec.rb6
-rw-r--r--spec/requests/api/admin/instance_clusters_spec.rb13
-rw-r--r--spec/requests/api/api_guard/admin_mode_middleware_spec.rb2
-rw-r--r--spec/requests/api/broadcast_messages_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/jobs_artifacts_spec.rb2
-rw-r--r--spec/requests/api/ci/runner/jobs_put_spec.rb17
-rw-r--r--spec/requests/api/ci/runner/jobs_trace_spec.rb2
-rw-r--r--spec/requests/api/composer_packages_spec.rb13
-rw-r--r--spec/requests/api/discussions_spec.rb31
-rw-r--r--spec/requests/api/feature_flags_spec.rb117
-rw-r--r--spec/requests/api/features_spec.rb178
-rw-r--r--spec/requests/api/go_proxy_spec.rb4
-rw-r--r--spec/requests/api/graphql/boards/board_lists_query_spec.rb16
-rw-r--r--spec/requests/api/graphql/ci/ci_cd_setting_spec.rb50
-rw-r--r--spec/requests/api/graphql/ci/config_spec.rb91
-rw-r--r--spec/requests/api/graphql/ci/job_artifacts_spec.rb52
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb119
-rw-r--r--spec/requests/api/graphql/group/container_repositories_spec.rb22
-rw-r--r--spec/requests/api/graphql/group/group_members_spec.rb80
-rw-r--r--spec/requests/api/graphql/group_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/issue/issue_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb12
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb120
-rw-r--r--spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb45
-rw-r--r--spec/requests/api/graphql/mutations/releases/delete_spec.rb132
-rw-r--r--spec/requests/api/graphql/mutations/releases/update_spec.rb255
-rw-r--r--spec/requests/api/graphql/mutations/snippets/create_spec.rb8
-rw-r--r--spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb10
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb44
-rw-r--r--spec/requests/api/graphql/project/container_repositories_spec.rb22
-rw-r--r--spec/requests/api/graphql/project/issue/design_collection/version_spec.rb3
-rw-r--r--spec/requests/api/graphql/project/issue_spec.rb6
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb28
-rw-r--r--spec/requests/api/graphql/project/merge_request/pipelines_spec.rb63
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb47
-rw-r--r--spec/requests/api/graphql/project/pipeline_spec.rb49
-rw-r--r--spec/requests/api/graphql/project/project_members_spec.rb121
-rw-r--r--spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb58
-rw-r--r--spec/requests/api/graphql/project/release_spec.rb10
-rw-r--r--spec/requests/api/graphql/project/terraform/states_spec.rb45
-rw-r--r--spec/requests/api/graphql/project_query_spec.rb35
-rw-r--r--spec/requests/api/graphql/user_query_spec.rb142
-rw-r--r--spec/requests/api/graphql/users_spec.rb14
-rw-r--r--spec/requests/api/graphql_spec.rb2
-rw-r--r--spec/requests/api/group_clusters_spec.rb12
-rw-r--r--spec/requests/api/group_import_spec.rb6
-rw-r--r--spec/requests/api/import_github_spec.rb4
-rw-r--r--spec/requests/api/internal/base_spec.rb17
-rw-r--r--spec/requests/api/internal/kubernetes_spec.rb16
-rw-r--r--spec/requests/api/internal/pages_spec.rb52
-rw-r--r--spec/requests/api/jobs_spec.rb53
-rw-r--r--spec/requests/api/merge_requests_spec.rb48
-rw-r--r--spec/requests/api/nuget_packages_spec.rb533
-rw-r--r--spec/requests/api/nuget_project_packages_spec.rb280
-rw-r--r--spec/requests/api/project_clusters_spec.rb12
-rw-r--r--spec/requests/api/project_import_spec.rb6
-rw-r--r--spec/requests/api/project_repository_storage_moves_spec.rb70
-rw-r--r--spec/requests/api/projects_spec.rb14
-rw-r--r--spec/requests/api/services_spec.rb4
-rw-r--r--spec/requests/api/settings_spec.rb25
-rw-r--r--spec/requests/api/usage_data_spec.rb81
-rw-r--r--spec/requests/api/users_spec.rb94
-rw-r--r--spec/requests/api/v3/github_spec.rb4
-rw-r--r--spec/requests/git_http_spec.rb20
-rw-r--r--spec/requests/ide_controller_spec.rb (renamed from spec/controllers/ide_controller_spec.rb)2
-rw-r--r--spec/requests/import/gitlab_groups_controller_spec.rb67
-rw-r--r--spec/requests/import/gitlab_projects_controller_spec.rb53
-rw-r--r--spec/requests/jwt_controller_spec.rb318
-rw-r--r--spec/requests/lfs_http_spec.rb1662
-rw-r--r--spec/requests/projects/cycle_analytics_events_spec.rb143
-rw-r--r--spec/requests/projects/metrics_dashboard_spec.rb4
-rw-r--r--spec/requests/rack_attack_global_spec.rb33
-rw-r--r--spec/requests/self_monitoring_project_spec.rb8
-rw-r--r--spec/requests/whats_new_controller_spec.rb34
-rw-r--r--spec/routing/git_http_routing_spec.rb58
-rw-r--r--spec/routing/group_routing_spec.rb4
-rw-r--r--spec/routing/import_routing_spec.rb26
-rw-r--r--spec/routing/routing_spec.rb39
-rw-r--r--spec/rubocop/cop/gitlab/policy_rule_boolean_spec.rb52
-rw-r--r--spec/rubocop/cop/graphql/descriptions_spec.rb102
-rw-r--r--spec/rubocop/cop/include_sidekiq_worker_spec.rb2
-rw-r--r--spec/rubocop/cop/lint/last_keyword_argument_spec.rb138
-rw-r--r--spec/rubocop/cop/migration/add_column_with_default_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/htt_party_basic_auth_spec.rb42
-rw-r--r--spec/serializers/accessibility_reports_comparer_entity_spec.rb4
-rw-r--r--spec/serializers/admin/user_entity_spec.rb31
-rw-r--r--spec/serializers/admin/user_serializer_spec.rb26
-rw-r--r--spec/serializers/board_simple_entity_spec.rb16
-rw-r--r--spec/serializers/codequality_degradation_entity_spec.rb88
-rw-r--r--spec/serializers/codequality_reports_comparer_entity_spec.rb85
-rw-r--r--spec/serializers/codequality_reports_comparer_serializer_spec.rb92
-rw-r--r--spec/serializers/environment_entity_spec.rb58
-rw-r--r--spec/serializers/import/bulk_import_entity_spec.rb5
-rw-r--r--spec/serializers/merge_request_current_user_entity_spec.rb21
-rw-r--r--spec/serializers/merge_request_user_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_widget_entity_spec.rb68
-rw-r--r--spec/serializers/paginated_diff_entity_spec.rb4
-rw-r--r--spec/serializers/rollout_status_entity_spec.rb53
-rw-r--r--spec/serializers/rollout_statuses/ingress_entity_spec.rb19
-rw-r--r--spec/services/alert_management/process_prometheus_alert_service_spec.rb2
-rw-r--r--spec/services/application_settings/update_service_spec.rb28
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb990
-rw-r--r--spec/services/auth/dependency_proxy_authentication_service_spec.rb46
-rw-r--r--spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb2
-rw-r--r--spec/services/boards/lists/create_service_spec.rb50
-rw-r--r--spec/services/ci/compare_accessibility_reports_service_spec.rb30
-rw-r--r--spec/services/ci/compare_codequality_reports_service_spec.rb32
-rw-r--r--spec/services/ci/compare_reports_base_service_spec.rb38
-rw-r--r--spec/services/ci/compare_test_reports_service_spec.rb32
-rw-r--r--spec/services/ci/create_job_artifacts_service_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/merge_requests_spec.rb53
-rw-r--r--spec/services/ci/create_pipeline_service/rules_spec.rb142
-rw-r--r--spec/services/ci/generate_coverage_reports_service_spec.rb30
-rw-r--r--spec/services/ci/generate_terraform_reports_service_spec.rb29
-rw-r--r--spec/services/ci/list_config_variables_service_spec.rb47
-rw-r--r--spec/services/ci/pipelines/create_artifact_service_spec.rb3
-rw-r--r--spec/services/ci/test_cases_service_spec.rb94
-rw-r--r--spec/services/ci/test_failure_history_service_spec.rb192
-rw-r--r--spec/services/ci/update_build_state_service_spec.rb70
-rw-r--r--spec/services/clusters/applications/create_service_spec.rb32
-rw-r--r--spec/services/clusters/applications/prometheus_health_check_service_spec.rb6
-rw-r--r--spec/services/clusters/aws/authorize_role_service_spec.rb15
-rw-r--r--spec/services/clusters/aws/fetch_credentials_service_spec.rb58
-rw-r--r--spec/services/clusters/aws/provision_service_spec.rb4
-rw-r--r--spec/services/clusters/cleanup/app_service_spec.rb5
-rw-r--r--spec/services/concerns/exclusive_lease_guard_spec.rb2
-rw-r--r--spec/services/container_expiration_policies/cleanup_service_spec.rb2
-rw-r--r--spec/services/dependency_proxy/auth_token_service_spec.rb37
-rw-r--r--spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb83
-rw-r--r--spec/services/dependency_proxy/head_manifest_service_spec.rb44
-rw-r--r--spec/services/dependency_proxy/pull_manifest_service_spec.rb51
-rw-r--r--spec/services/environments/canary_ingress/update_service_spec.rb139
-rw-r--r--spec/services/event_create_service_spec.rb4
-rw-r--r--spec/services/files/delete_service_spec.rb2
-rw-r--r--spec/services/files/update_service_spec.rb6
-rw-r--r--spec/services/git/branch_push_service_spec.rb4
-rw-r--r--spec/services/git/tag_hooks_service_spec.rb2
-rw-r--r--spec/services/groups/import_export/import_service_spec.rb2
-rw-r--r--spec/services/groups/transfer_service_spec.rb71
-rw-r--r--spec/services/incident_management/incidents/create_service_spec.rb2
-rw-r--r--spec/services/issues/clone_service_spec.rb340
-rw-r--r--spec/services/issues/create_service_spec.rb1
-rw-r--r--spec/services/issues/update_service_spec.rb83
-rw-r--r--spec/services/jira/requests/projects/list_service_spec.rb27
-rw-r--r--spec/services/jira_connect/sync_service_spec.rb33
-rw-r--r--spec/services/members/approve_access_request_service_spec.rb10
-rw-r--r--spec/services/members/create_service_spec.rb83
-rw-r--r--spec/services/members/invitation_reminder_email_service_spec.rb96
-rw-r--r--spec/services/merge_requests/after_create_service_spec.rb19
-rw-r--r--spec/services/merge_requests/base_service_spec.rb3
-rw-r--r--spec/services/metrics/dashboard/clone_dashboard_service_spec.rb2
-rw-r--r--spec/services/notification_service_spec.rb47
-rw-r--r--spec/services/onboarding_progress_service_spec.rb33
-rw-r--r--spec/services/packages/composer/create_package_service_spec.rb4
-rw-r--r--spec/services/packages/conan/create_package_file_service_spec.rb13
-rw-r--r--spec/services/packages/conan/create_package_service_spec.rb1
-rw-r--r--spec/services/packages/create_event_service_spec.rb24
-rw-r--r--spec/services/packages/create_package_file_service_spec.rb21
-rw-r--r--spec/services/packages/generic/create_package_file_service_spec.rb8
-rw-r--r--spec/services/packages/nuget/create_package_service_spec.rb1
-rw-r--r--spec/services/packages/pypi/create_package_service_spec.rb2
-rw-r--r--spec/services/pages/legacy_storage_lease_spec.rb73
-rw-r--r--spec/services/pages/zip_directory_service_spec.rb209
-rw-r--r--spec/services/post_receive_service_spec.rb13
-rw-r--r--spec/services/projects/alerting/notify_service_spec.rb5
-rw-r--r--spec/services/projects/container_repository/delete_tags_service_spec.rb4
-rw-r--r--spec/services/projects/fork_service_spec.rb6
-rw-r--r--spec/services/projects/git_deduplication_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/migrate_repository_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/rollback_repository_service_spec.rb2
-rw-r--r--spec/services/projects/move_access_service_spec.rb6
-rw-r--r--spec/services/projects/move_deploy_keys_projects_service_spec.rb2
-rw-r--r--spec/services/projects/move_lfs_objects_projects_service_spec.rb2
-rw-r--r--spec/services/projects/move_notification_settings_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_authorizations_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_group_links_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_members_service_spec.rb2
-rw-r--r--spec/services/projects/open_issues_count_service_spec.rb8
-rw-r--r--spec/services/projects/participants_service_spec.rb113
-rw-r--r--spec/services/projects/prometheus/alerts/notify_service_spec.rb17
-rw-r--r--spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb47
-rw-r--r--spec/services/projects/transfer_service_spec.rb27
-rw-r--r--spec/services/projects/update_pages_service_spec.rb30
-rw-r--r--spec/services/projects/update_repository_storage_service_spec.rb12
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb4
-rw-r--r--spec/services/releases/create_service_spec.rb69
-rw-r--r--spec/services/resource_events/change_labels_service_spec.rb25
-rw-r--r--spec/services/submit_usage_ping_service_spec.rb15
-rw-r--r--spec/services/suggestions/apply_service_spec.rb2
-rw-r--r--spec/services/system_hooks_service_spec.rb3
-rw-r--r--spec/services/system_note_service_spec.rb13
-rw-r--r--spec/services/system_notes/issuables_service_spec.rb85
-rw-r--r--spec/services/users/create_service_spec.rb2
-rw-r--r--spec/services/users/reject_service_spec.rb54
-rw-r--r--spec/services/users/set_status_service_spec.rb39
-rw-r--r--spec/services/users/validate_otp_service_spec.rb18
-rw-r--r--spec/spec_helper.rb39
-rw-r--r--spec/support/atlassian/jira_connect/schemata.rb83
-rw-r--r--spec/support/caching.rb2
-rw-r--r--spec/support/gitlab_experiment.rb4
-rw-r--r--spec/support/gitlab_stubs/gitlab_ci_includes.yml19
-rw-r--r--spec/support/graphql/arguments.rb70
-rw-r--r--spec/support/graphql/field_inspection.rb35
-rw-r--r--spec/support/graphql/field_selection.rb73
-rw-r--r--spec/support/graphql/var.rb59
-rw-r--r--spec/support/helpers/after_next_helpers.rb53
-rw-r--r--spec/support/helpers/database_helpers.rb13
-rw-r--r--spec/support/helpers/dependency_proxy_helpers.rb18
-rw-r--r--spec/support/helpers/features/merge_request_helpers.rb25
-rw-r--r--spec/support/helpers/features/web_ide_spec_helpers.rb35
-rw-r--r--spec/support/helpers/file_read_helpers.rb48
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb4
-rw-r--r--spec/support/helpers/git_http_helpers.rb34
-rw-r--r--spec/support/helpers/gitlab_verify_helpers.rb2
-rw-r--r--spec/support/helpers/gpg_helpers.rb139
-rw-r--r--spec/support/helpers/graphql_helpers.rb206
-rw-r--r--spec/support/helpers/login_helpers.rb16
-rw-r--r--spec/support/helpers/multipart_helpers.rb2
-rw-r--r--spec/support/helpers/next_instance_of.rb27
-rw-r--r--spec/support/helpers/services_helper.rb11
-rw-r--r--spec/support/helpers/snowplow_helpers.rb26
-rw-r--r--spec/support/helpers/stub_experiments.rb16
-rw-r--r--spec/support/helpers/stub_object_storage.rb17
-rw-r--r--spec/support/helpers/test_env.rb48
-rw-r--r--spec/support/helpers/usage_data_helpers.rb2
-rw-r--r--spec/support/helpers/workhorse_helpers.rb6
-rw-r--r--spec/support/import_export/common_util.rb5
-rw-r--r--spec/support/matchers/access_matchers.rb4
-rw-r--r--spec/support/matchers/be_valid_json.rb32
-rw-r--r--spec/support/matchers/exceed_query_limit.rb121
-rw-r--r--spec/support/services/issuable_import_csv_service_shared_examples.rb32
-rw-r--r--spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb14
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb6
-rw-r--r--spec/support/shared_contexts/issuable/merge_request_shared_context.rb44
-rw-r--r--spec/support/shared_contexts/lib/gitlab/middleware/with_a_mocked_gitlab_instance_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb26
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb1
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb5
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb117
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb68
-rw-r--r--spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb1
-rw-r--r--spec/support/shared_examples/features/reportable_note_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/features/wiki/user_uses_wiki_shortcuts_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/connection_redaction_shared_examples.rb54
-rw-r--r--spec/support/shared_examples/graphql/connection_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/graphql/design_fields_shared_examples.rb30
-rw-r--r--spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/graphql/members_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/graphql/mutation_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/graphql/mutations/spammable_mutation_fields_examples.rb7
-rw-r--r--spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb93
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/lib/gitlab/import_export/import_failure_service_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/models/concerns/can_move_repository_storage_shared_examples.rb55
-rw-r--r--spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb115
-rw-r--r--spec/support/shared_examples/models/concerns/shardable_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/models/mentionable_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/resource_timebox_event_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb187
-rw-r--r--spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb265
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/requests/graphql_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/requests/lfs_http_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb114
-rw-r--r--spec/support/shared_examples/requests/self_monitoring_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/requests/uploads_auhorize_shared_examples.rb61
-rw-r--r--spec/support/shared_examples/routing/git_http_routing_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/services/alert_management_shared_examples.rb32
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb1006
-rw-r--r--spec/support/shared_examples/services/incident_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/workers/project_export_shared_examples.rb82
-rw-r--r--spec/support/snowplow.rb3
-rw-r--r--spec/support_specs/graphql/arguments_spec.rb71
-rw-r--r--spec/support_specs/graphql/field_selection_spec.rb62
-rw-r--r--spec/support_specs/graphql/var_spec.rb34
-rw-r--r--spec/support_specs/helpers/graphql_helpers_spec.rb274
-rw-r--r--spec/support_specs/helpers/stub_feature_flags_spec.rb25
-rw-r--r--spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb226
-rw-r--r--spec/tasks/gettext_rake_spec.rb177
-rw-r--r--spec/tasks/gitlab/db_rake_spec.rb48
-rw-r--r--spec/tasks/gitlab/ldap_rake_spec.rb109
-rw-r--r--spec/tasks/gitlab/packages/events_rake_spec.rb43
-rw-r--r--spec/tasks/gitlab/user_management_rake_spec.rb83
-rw-r--r--spec/tasks/gitlab/workhorse_rake_spec.rb68
-rw-r--r--spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb92
-rw-r--r--spec/tooling/lib/tooling/test_map_generator_spec.rb25
-rw-r--r--spec/uploaders/object_storage_spec.rb2
-rw-r--r--spec/uploaders/terraform/state_uploader_spec.rb36
-rw-r--r--spec/uploaders/terraform/versioned_state_uploader_spec.rb47
-rw-r--r--spec/validators/json_schema_validator_spec.rb30
-rw-r--r--spec/views/admin/application_settings/general.html.haml_spec.rb28
-rw-r--r--spec/views/admin/dashboard/index.html.haml_spec.rb1
-rw-r--r--spec/views/layouts/_head.html.haml_spec.rb14
-rw-r--r--spec/views/projects/artifacts/_artifact.html.haml_spec.rb17
-rw-r--r--spec/views/projects/commits/_commit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/empty.html.haml_spec.rb82
-rw-r--r--spec/views/projects/merge_requests/show.html.haml_spec.rb39
-rw-r--r--spec/views/search/_filter.html.haml_spec.rb2
-rw-r--r--spec/views/search/_results.html.haml_spec.rb46
-rw-r--r--spec/views/shared/wikis/_sidebar.html.haml_spec.rb83
-rw-r--r--spec/workers/approve_blocked_pending_approval_users_worker_spec.rb31
-rw-r--r--spec/workers/build_finished_worker_spec.rb3
-rw-r--r--spec/workers/ci/test_failure_history_worker_spec.rb47
-rw-r--r--spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/object_importer_spec.rb72
-rw-r--r--spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb72
-rw-r--r--spec/workers/concerns/reenqueuer_spec.rb8
-rw-r--r--spec/workers/environments/canary_ingress/update_worker_spec.rb33
-rw-r--r--spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_issue_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb23
-rw-r--r--spec/workers/gitlab/github_import/import_pull_request_review_worker_spec.rb23
-rw-r--r--spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb12
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb35
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_reviews_worker_spec.rb52
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_performance_bar_stats_worker_spec.rb30
-rw-r--r--spec/workers/gitlab_usage_ping_worker_spec.rb7
-rw-r--r--spec/workers/import_issues_csv_worker_spec.rb10
-rw-r--r--spec/workers/jira_connect/sync_branch_worker_spec.rb9
-rw-r--r--spec/workers/jira_connect/sync_builds_worker_spec.rb60
-rw-r--r--spec/workers/jira_connect/sync_merge_request_worker_spec.rb9
-rw-r--r--spec/workers/jira_connect/sync_project_worker_spec.rb10
-rw-r--r--spec/workers/member_invitation_reminder_emails_worker_spec.rb26
-rw-r--r--spec/workers/namespaces/onboarding_user_added_worker_spec.rb16
-rw-r--r--spec/workers/post_receive_spec.rb83
-rw-r--r--spec/workers/project_cache_worker_spec.rb14
-rw-r--r--spec/workers/project_export_worker_spec.rb81
-rw-r--r--spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb33
-rw-r--r--spec/workers/purge_dependency_proxy_cache_worker_spec.rb8
-rw-r--r--spec/workers/releases/create_evidence_worker_spec.rb (renamed from spec/workers/create_evidence_worker_spec.rb)2
-rw-r--r--spec/workers/releases/manage_evidence_worker_spec.rb43
-rw-r--r--spec/workers/repository_import_worker_spec.rb2
-rw-r--r--spec/workers/repository_remove_remote_worker_spec.rb2
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb9
1508 files changed, 48887 insertions, 18353 deletions
diff --git a/spec/config/object_store_settings_spec.rb b/spec/config/object_store_settings_spec.rb
index 1777213f481..9e7dfa043c3 100644
--- a/spec/config/object_store_settings_spec.rb
+++ b/spec/config/object_store_settings_spec.rb
@@ -59,6 +59,7 @@ RSpec.describe ObjectStoreSettings do
expect(settings.artifacts['object_store']['background_upload']).to be false
expect(settings.artifacts['object_store']['proxy_download']).to be false
expect(settings.artifacts['object_store']['remote_directory']).to eq('artifacts')
+ expect(settings.artifacts['object_store']['consolidated_settings']).to be true
expect(settings.lfs['enabled']).to be true
expect(settings.lfs['object_store']['enabled']).to be true
@@ -67,15 +68,18 @@ RSpec.describe ObjectStoreSettings do
expect(settings.lfs['object_store']['background_upload']).to be false
expect(settings.lfs['object_store']['proxy_download']).to be true
expect(settings.lfs['object_store']['remote_directory']).to eq('lfs-objects')
+ expect(settings.lfs['object_store']['consolidated_settings']).to be true
expect(settings.pages['enabled']).to be true
expect(settings.pages['object_store']['enabled']).to be true
expect(settings.pages['object_store']['connection']).to eq(connection)
expect(settings.pages['object_store']['remote_directory']).to eq('pages')
+ expect(settings.pages['object_store']['consolidated_settings']).to be true
expect(settings.external_diffs['enabled']).to be false
expect(settings.external_diffs['object_store']['enabled']).to be false
expect(settings.external_diffs['object_store']['remote_directory']).to eq('external_diffs')
+ expect(settings.external_diffs['object_store']['consolidated_settings']).to be true
end
it 'raises an error when a bucket is missing' do
@@ -91,6 +95,31 @@ RSpec.describe ObjectStoreSettings do
expect(settings.pages['object_store']).to eq(nil)
end
+ it 'allows pages to define its own connection' do
+ pages_connection = { 'provider' => 'Google', 'google_application_default' => true }
+ config['pages'] = {
+ 'enabled' => true,
+ 'object_store' => {
+ 'enabled' => true,
+ 'connection' => pages_connection
+ }
+ }
+
+ expect { subject }.not_to raise_error
+
+ described_class::WORKHORSE_ACCELERATED_TYPES.each do |object_type|
+ section = settings.try(object_type)
+
+ next unless section
+
+ expect(section['object_store']['connection']).to eq(connection)
+ expect(section['object_store']['consolidated_settings']).to be true
+ end
+
+ expect(settings.pages['object_store']['connection']).to eq(pages_connection)
+ expect(settings.pages['object_store']['consolidated_settings']).to be_falsey
+ end
+
context 'with legacy config' do
let(:legacy_settings) do
{
diff --git a/spec/config/settings_spec.rb b/spec/config/settings_spec.rb
index ed873478fc9..6525ae653c9 100644
--- a/spec/config/settings_spec.rb
+++ b/spec/config/settings_spec.rb
@@ -134,4 +134,20 @@ RSpec.describe Settings do
end
end
end
+
+ describe '.encrypted' do
+ before do
+ allow(Gitlab::Application.secrets).to receive(:encryped_settings_key_base).and_return(SecureRandom.hex(64))
+ end
+
+ it 'defaults to using the encrypted_settings_key_base for the key' do
+ expect(Gitlab::EncryptedConfiguration).to receive(:new).with(hash_including(base_key: Gitlab::Application.secrets.encrypted_settings_key_base))
+ Settings.encrypted('tmp/tests/test.enc')
+ end
+
+ it 'returns empty encrypted config when a key has not been set' do
+ allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
+ expect(Settings.encrypted('tmp/tests/test.enc').read).to be_empty
+ end
+ end
end
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index f71f859a704..f0b224484c6 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -157,6 +157,44 @@ RSpec.describe Admin::ApplicationSettingsController do
expect(ApplicationSetting.current.default_branch_name).to eq("example_branch_name")
end
+ context "personal access token prefix settings" do
+ let(:application_settings) { ApplicationSetting.current }
+
+ shared_examples "accepts prefix setting" do |prefix|
+ it "updates personal_access_token_prefix setting" do
+ put :update, params: { application_setting: { personal_access_token_prefix: prefix } }
+
+ expect(response).to redirect_to(general_admin_application_settings_path)
+ expect(application_settings.reload.personal_access_token_prefix).to eq(prefix)
+ end
+ end
+
+ shared_examples "rejects prefix setting" do |prefix|
+ it "does not update personal_access_token_prefix setting" do
+ put :update, params: { application_setting: { personal_access_token_prefix: prefix } }
+
+ expect(response).not_to redirect_to(general_admin_application_settings_path)
+ expect(application_settings.reload.personal_access_token_prefix).not_to eq(prefix)
+ end
+ end
+
+ context "with valid prefix" do
+ include_examples("accepts prefix setting", "a_prefix@")
+ end
+
+ context "with blank prefix" do
+ include_examples("accepts prefix setting", "")
+ end
+
+ context "with too long prefix" do
+ include_examples("rejects prefix setting", "a_prefix@" * 10)
+ end
+
+ context "with invalid characters prefix" do
+ include_examples("rejects prefix setting", "a_préfixñ:")
+ end
+ end
+
context 'external policy classification settings' do
let(:settings) do
{
diff --git a/spec/controllers/admin/clusters/applications_controller_spec.rb b/spec/controllers/admin/clusters/applications_controller_spec.rb
index 2a77693061c..d1ca64d6bd2 100644
--- a/spec/controllers/admin/clusters/applications_controller_spec.rb
+++ b/spec/controllers/admin/clusters/applications_controller_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Admin::Clusters::ApplicationsController do
post :create, params: params
end
- let(:application) { 'helm' }
+ let(:application) { 'ingress' }
let(:params) { { application: application, id: cluster.id } }
describe 'functionality' do
@@ -37,7 +37,7 @@ RSpec.describe Admin::Clusters::ApplicationsController do
expect { subject }.to change { current_application.count }
expect(response).to have_gitlab_http_status(:no_content)
- expect(cluster.application_helm).to be_scheduled
+ expect(cluster.application_ingress).to be_scheduled
end
context 'when cluster do not exists' do
@@ -61,7 +61,7 @@ RSpec.describe Admin::Clusters::ApplicationsController do
context 'when application is already installing' do
before do
- create(:clusters_applications_helm, :installing, cluster: cluster)
+ create(:clusters_applications_ingress, :installing, cluster: cluster)
end
it 'returns 400' do
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb
index 69bdc79c5f5..85aa77d8473 100644
--- a/spec/controllers/admin/clusters_controller_spec.rb
+++ b/spec/controllers/admin/clusters_controller_spec.rb
@@ -341,7 +341,7 @@ RSpec.describe Admin::ClustersController do
expect { post_create_aws }.not_to change { Clusters::Cluster.count }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
- expect(response.content_type).to eq('application/json')
+ expect(response.media_type).to eq('application/json')
expect(response.body).to include('is invalid')
end
end
diff --git a/spec/controllers/admin/integrations_controller_spec.rb b/spec/controllers/admin/integrations_controller_spec.rb
index 1a13d016b73..e14619a9916 100644
--- a/spec/controllers/admin/integrations_controller_spec.rb
+++ b/spec/controllers/admin/integrations_controller_spec.rb
@@ -73,4 +73,28 @@ RSpec.describe Admin::IntegrationsController do
end
end
end
+
+ describe '#reset' do
+ let_it_be(:integration) { create(:jira_service, :instance) }
+ let_it_be(:inheriting_integration) { create(:jira_service, inherit_from_id: integration.id) }
+
+ subject do
+ post :reset, params: { id: integration.class.to_param }
+ end
+
+ it 'returns 200 OK', :aggregate_failures do
+ subject
+
+ expected_json = {}.to_json
+
+ expect(flash[:notice]).to eq('This integration, and inheriting projects were reset.')
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(expected_json)
+ end
+
+ it 'deletes the integration and all inheriting integrations' do
+ expect { subject }.to change { JiraService.for_instance.count }.by(-1)
+ .and change { JiraService.inherit_from_id(integration.id).count }.by(-1)
+ end
+ end
end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index d0d1fa6a6bc..f902a3d2541 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -102,6 +102,57 @@ RSpec.describe Admin::UsersController do
end
end
+ describe 'DELETE #reject' do
+ subject { put :reject, params: { id: user.username } }
+
+ context 'when rejecting a pending user' do
+ let(:user) { create(:user, :blocked_pending_approval) }
+
+ it 'hard deletes the user', :sidekiq_inline do
+ subject
+
+ expect(User.exists?(user.id)).to be_falsy
+ end
+
+ it 'displays the rejection message' do
+ subject
+
+ expect(response).to redirect_to(admin_users_path)
+ expect(flash[:notice]).to eq("You've rejected #{user.name}")
+ end
+
+ it 'sends the user a rejection email' do
+ expect_next_instance_of(NotificationService) do |notification|
+ allow(notification).to receive(:user_admin_rejection).with(user.name, user.notification_email)
+ end
+
+ subject
+ end
+ end
+
+ context 'when user is not pending' do
+ let(:user) { create(:user, state: 'active') }
+
+ it 'does not reject and delete the user' do
+ subject
+
+ expect(User.exists?(user.id)).to be_truthy
+ end
+
+ it 'displays the error' do
+ subject
+
+ expect(flash[:alert]).to eq('This user does not have a pending request')
+ end
+
+ it 'does not email the user' do
+ expect(NotificationService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+
describe 'PUT #approve' do
let(:user) { create(:user, :blocked_pending_approval) }
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index 9b09f46d17e..29141582c6f 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Boards::ListsController do
read_board_list user: user, board: board
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
end
it 'returns a list of board lists' do
@@ -85,20 +85,22 @@ RSpec.describe Boards::ListsController do
context 'with invalid params' do
context 'when label is nil' do
- it 'returns a not found 404 response' do
+ it 'returns an unprocessable entity 422 response' do
create_board_list user: user, board: board, label_id: nil
- expect(response).to have_gitlab_http_status(:not_found)
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['errors']).to eq(['Label not found'])
end
end
context 'when label that does not belongs to project' do
- it 'returns a not found 404 response' do
+ it 'returns an unprocessable entity 422 response' do
label = create(:label, name: 'Development')
create_board_list user: user, board: board, label_id: label.id
- expect(response).to have_gitlab_http_status(:not_found)
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['errors']).to eq(['Label not found'])
end
end
end
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index 9b78f841cce..c838affa239 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -15,16 +15,6 @@ RSpec.describe DashboardController do
describe 'GET issues' do
it_behaves_like 'issuables list meta-data', :issue, :issues
it_behaves_like 'issuables requiring filter', :issues
-
- it 'lists only incidents and issues' do
- issue = create(:incident, project: project, author: user)
- incident = create(:incident, project: project, author: user)
- create(:quality_test_case, project: project, author: user)
-
- get :issues, params: { author_id: user.id }
-
- expect(assigns(:issues)).to match_array([issue, incident])
- end
end
describe 'GET merge requests' do
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb
index 66595c27531..a7480130e0a 100644
--- a/spec/controllers/groups/boards_controller_spec.rb
+++ b/spec/controllers/groups/boards_controller_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe Groups::BoardsController do
list_boards
expect(response).to render_template :index
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
context 'with unauthorized user' do
@@ -36,7 +36,7 @@ RSpec.describe Groups::BoardsController do
list_boards
expect(response).to have_gitlab_http_status(:not_found)
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
end
@@ -52,7 +52,7 @@ RSpec.describe Groups::BoardsController do
list_boards
expect(response).to render_template :index
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
end
end
@@ -81,7 +81,7 @@ RSpec.describe Groups::BoardsController do
list_boards format: :json
expect(response).to have_gitlab_http_status(:not_found)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
end
end
end
@@ -103,7 +103,7 @@ RSpec.describe Groups::BoardsController do
expect { read_board board: board }.to change(BoardGroupRecentVisit, :count).by(1)
expect(response).to render_template :show
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
context 'with unauthorized user' do
@@ -118,7 +118,7 @@ RSpec.describe Groups::BoardsController do
read_board board: board
expect(response).to have_gitlab_http_status(:not_found)
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
end
@@ -131,7 +131,7 @@ RSpec.describe Groups::BoardsController do
expect { read_board board: board }.to change(BoardGroupRecentVisit, :count).by(0)
expect(response).to render_template :show
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
end
end
@@ -157,7 +157,7 @@ RSpec.describe Groups::BoardsController do
read_board board: board, format: :json
expect(response).to have_gitlab_http_status(:not_found)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
end
end
end
diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb
index c1d170edce3..c3947c27399 100644
--- a/spec/controllers/groups/clusters/applications_controller_spec.rb
+++ b/spec/controllers/groups/clusters/applications_controller_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Groups::Clusters::ApplicationsController do
post :create, params: params.merge(group_id: group)
end
- let(:application) { 'helm' }
+ let(:application) { 'ingress' }
let(:params) { { application: application, id: cluster.id } }
describe 'functionality' do
@@ -44,7 +44,7 @@ RSpec.describe Groups::Clusters::ApplicationsController do
expect { subject }.to change { current_application.count }
expect(response).to have_gitlab_http_status(:no_content)
- expect(cluster.application_helm).to be_scheduled
+ expect(cluster.application_ingress).to be_scheduled
end
context 'when cluster do not exists' do
@@ -68,7 +68,7 @@ RSpec.describe Groups::Clusters::ApplicationsController do
context 'when application is already installing' do
before do
- create(:clusters_applications_helm, :installing, cluster: cluster)
+ create(:clusters_applications_ingress, :installing, cluster: cluster)
end
it 'returns 400' do
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index 140b7b0f2a8..b287aca1e46 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -476,7 +476,7 @@ RSpec.describe Groups::ClustersController do
expect { post_create_aws }.not_to change { Clusters::Cluster.count }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
- expect(response.content_type).to eq('application/json')
+ expect(response.media_type).to eq('application/json')
expect(response.body).to include('is invalid')
end
end
diff --git a/spec/controllers/groups/dependency_proxy_auth_controller_spec.rb b/spec/controllers/groups/dependency_proxy_auth_controller_spec.rb
new file mode 100644
index 00000000000..857e0570621
--- /dev/null
+++ b/spec/controllers/groups/dependency_proxy_auth_controller_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::DependencyProxyAuthController do
+ include DependencyProxyHelpers
+
+ describe 'GET #authenticate' do
+ subject { get :authenticate }
+
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(dependency_proxy_for_private_groups: false)
+ end
+
+ it 'returns successfully', :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+ end
+
+ context 'without JWT' do
+ it 'returns unauthorized with oauth realm', :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(response.headers['WWW-Authenticate']).to eq DependencyProxy::Registry.authenticate_header
+ end
+ end
+
+ context 'with valid JWT' do
+ let_it_be(:user) { create(:user) }
+ let(:jwt) { build_jwt(user) }
+ let(:token_header) { "Bearer #{jwt.encoded}" }
+
+ before do
+ request.headers['HTTP_AUTHORIZATION'] = token_header
+ end
+
+ it { is_expected.to have_gitlab_http_status(:success) }
+ end
+
+ context 'with invalid JWT' do
+ context 'bad user' do
+ let(:jwt) { build_jwt(double('bad_user', id: 999)) }
+ let(:token_header) { "Bearer #{jwt.encoded}" }
+
+ before do
+ request.headers['HTTP_AUTHORIZATION'] = token_header
+ end
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'token with no user id' do
+ let(:token_header) { "Bearer #{build_jwt.encoded}" }
+
+ before do
+ request.headers['HTTP_AUTHORIZATION'] = token_header
+ end
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'expired token' do
+ let_it_be(:user) { create(:user) }
+ let(:jwt) { build_jwt(user, expire_time: Time.zone.now - 1.hour) }
+ let(:token_header) { "Bearer #{jwt.encoded}" }
+
+ before do
+ request.headers['HTTP_AUTHORIZATION'] = token_header
+ end
+
+ it { is_expected.to have_gitlab_http_status(:unauthorized) }
+ end
+ end
+ end
+end
diff --git a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
index 615b56ff22f..39cbdfb9123 100644
--- a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
+++ b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
@@ -3,8 +3,77 @@
require 'spec_helper'
RSpec.describe Groups::DependencyProxyForContainersController do
+ include HttpBasicAuthHelpers
+ include DependencyProxyHelpers
+
+ let_it_be(:user) { create(:user) }
let(:group) { create(:group) }
let(:token_response) { { status: :success, token: 'abcd1234' } }
+ let(:jwt) { build_jwt(user) }
+ let(:token_header) { "Bearer #{jwt.encoded}" }
+
+ shared_examples 'without a token' do
+ before do
+ request.headers['HTTP_AUTHORIZATION'] = nil
+ end
+
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(dependency_proxy_for_private_groups: false)
+ end
+
+ it { is_expected.to have_gitlab_http_status(:ok) }
+ end
+
+ it { is_expected.to have_gitlab_http_status(:unauthorized) }
+ end
+
+ shared_examples 'feature flag disabled with private group' do
+ before do
+ stub_feature_flags(dependency_proxy_for_private_groups: false)
+ end
+
+ it 'redirects', :aggregate_failures do
+ group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ expect(response.location).to end_with(new_user_session_path)
+ end
+ end
+
+ shared_examples 'without permission' do
+ context 'with invalid user' do
+ before do
+ user = double('bad_user', id: 999)
+ token_header = "Bearer #{build_jwt(user).encoded}"
+ request.headers['HTTP_AUTHORIZATION'] = token_header
+ end
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'with valid user that does not have access' do
+ let(:group) { create(:group, :private) }
+
+ before do
+ user = double('bad_user', id: 999)
+ token_header = "Bearer #{build_jwt(user).encoded}"
+ request.headers['HTTP_AUTHORIZATION'] = token_header
+ end
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'when user is not found' do
+ before do
+ allow(User).to receive(:find).and_return(nil)
+ end
+
+ it { is_expected.to have_gitlab_http_status(:unauthorized) }
+ end
+ end
shared_examples 'not found when disabled' do
context 'feature disabled' do
@@ -27,14 +96,16 @@ RSpec.describe Groups::DependencyProxyForContainersController do
allow_next_instance_of(DependencyProxy::RequestTokenService) do |instance|
allow(instance).to receive(:execute).and_return(token_response)
end
+
+ request.headers['HTTP_AUTHORIZATION'] = token_header
end
describe 'GET #manifest' do
- let(:manifest) { { foo: 'bar' }.to_json }
+ let_it_be(:manifest) { create(:dependency_proxy_manifest) }
let(:pull_response) { { status: :success, manifest: manifest } }
before do
- allow_next_instance_of(DependencyProxy::PullManifestService) do |instance|
+ allow_next_instance_of(DependencyProxy::FindOrCreateManifestService) do |instance|
allow(instance).to receive(:execute).and_return(pull_response)
end
end
@@ -46,6 +117,10 @@ RSpec.describe Groups::DependencyProxyForContainersController do
enable_dependency_proxy
end
+ it_behaves_like 'without a token'
+ it_behaves_like 'without permission'
+ it_behaves_like 'feature flag disabled with private group'
+
context 'remote token request fails' do
let(:token_response) do
{
@@ -80,11 +155,17 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
end
- it 'returns 200 with manifest file' do
+ it 'sends a file' do
+ expect(controller).to receive(:send_file).with(manifest.file.path, {})
+
+ subject
+ end
+
+ it 'returns Content-Disposition: attachment' do
subject
expect(response).to have_gitlab_http_status(:ok)
- expect(response.body).to eq(manifest)
+ expect(response.headers['Content-Disposition']).to match(/^attachment/)
end
end
@@ -96,7 +177,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
describe 'GET #blob' do
- let(:blob) { create(:dependency_proxy_blob) }
+ let_it_be(:blob) { create(:dependency_proxy_blob) }
let(:blob_sha) { blob.file_name.sub('.gz', '') }
let(:blob_response) { { status: :success, blob: blob } }
@@ -113,6 +194,10 @@ RSpec.describe Groups::DependencyProxyForContainersController do
enable_dependency_proxy
end
+ it_behaves_like 'without a token'
+ it_behaves_like 'without permission'
+ it_behaves_like 'feature flag disabled with private group'
+
context 'remote blob request fails' do
let(:blob_response) do
{
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index 2c85fe482e2..05e93da18e7 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -177,7 +177,7 @@ RSpec.describe Groups::MilestonesController do
expect(milestones.count).to eq(3)
expect(milestones.collect { |m| m['title'] }).to match_array(['same name', 'same name', 'group milestone'])
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
end
context 'with subgroup milestones' do
diff --git a/spec/controllers/groups/releases_controller_spec.rb b/spec/controllers/groups/releases_controller_spec.rb
index 0925548f60a..50701382945 100644
--- a/spec/controllers/groups/releases_controller_spec.rb
+++ b/spec/controllers/groups/releases_controller_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Groups::ReleasesController do
end
it 'returns an application/json content_type' do
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
end
it 'returns OK' do
diff --git a/spec/controllers/groups/settings/integrations_controller_spec.rb b/spec/controllers/groups/settings/integrations_controller_spec.rb
index beb2ad3afec..3233e814184 100644
--- a/spec/controllers/groups/settings/integrations_controller_spec.rb
+++ b/spec/controllers/groups/settings/integrations_controller_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe Groups::Settings::IntegrationsController do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
before do
sign_in(user)
@@ -24,16 +24,6 @@ RSpec.describe Groups::Settings::IntegrationsController do
group.add_owner(user)
end
- context 'when group_level_integrations not enabled' do
- it 'returns not_found' do
- stub_feature_flags(group_level_integrations: false)
-
- get :index, params: { group_id: group }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
it 'successfully displays the template' do
get :index, params: { group_id: group }
@@ -57,16 +47,6 @@ RSpec.describe Groups::Settings::IntegrationsController do
group.add_owner(user)
end
- context 'when group_level_integrations not enabled' do
- it 'returns not_found' do
- stub_feature_flags(group_level_integrations: false)
-
- get :edit, params: { group_id: group, id: Service.available_services_names(include_project_specific: false).sample }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
Service.available_services_names(include_project_specific: false).each do |integration_name|
context "#{integration_name}" do
it 'successfully displays the template' do
@@ -111,4 +91,42 @@ RSpec.describe Groups::Settings::IntegrationsController do
end
end
end
+
+ describe '#reset' do
+ let_it_be(:integration) { create(:jira_service, group: group, project: nil) }
+ let_it_be(:inheriting_integration) { create(:jira_service, inherit_from_id: integration.id) }
+
+ subject do
+ post :reset, params: { group_id: group, id: integration.class.to_param }
+ end
+
+ context 'when user is not owner' do
+ it 'renders not_found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when user is owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'returns 200 OK', :aggregate_failures do
+ subject
+
+ expected_json = {}.to_json
+
+ expect(flash[:notice]).to eq('This integration, and inheriting projects were reset.')
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(expected_json)
+ end
+
+ it 'deletes the integration and all inheriting integrations' do
+ expect { subject }.to change { JiraService.for_group(group.id).count }.by(-1)
+ .and change { JiraService.inherit_from_id(integration.id).count }.by(-1)
+ end
+ end
+ end
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 55833ee3aad..939c36a98b2 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -333,7 +333,7 @@ RSpec.describe GroupsController, factory_default: :keep do
context 'and the user is part of the control group' do
before do
- stub_experiment_for_user(onboarding_issues: false)
+ stub_experiment_for_subject(onboarding_issues: false)
end
it 'tracks the event with the "created_namespace" action with the "control_group" property', :snowplow do
@@ -350,7 +350,7 @@ RSpec.describe GroupsController, factory_default: :keep do
context 'and the user is part of the experimental group' do
before do
- stub_experiment_for_user(onboarding_issues: true)
+ stub_experiment_for_subject(onboarding_issues: true)
end
it 'tracks the event with the "created_namespace" action with the "experimental_group" property', :snowplow do
@@ -400,15 +400,6 @@ RSpec.describe GroupsController, factory_default: :keep do
sign_in(user)
end
- it 'lists only incidents and issues' do
- incident = create(:incident, project: project)
- create(:quality_test_case, project: project)
-
- get :issues, params: { id: group.to_param }
-
- expect(assigns(:issues)).to match_array([issue_1, issue_2, incident])
- end
-
context 'sorting by votes' do
it 'sorts most popular issues' do
get :issues, params: { id: group.to_param, sort: 'upvotes_desc' }
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 32b72eec0d6..7f55c4407dd 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -34,14 +34,14 @@ RSpec.describe HealthCheckController, :request_store do
get :index
expect(response).to be_successful
- expect(response.content_type).to eq 'text/plain'
+ expect(response.media_type).to eq 'text/plain'
end
it 'supports passing the token in query params' do
get :index, params: { token: token }
expect(response).to be_successful
- expect(response.content_type).to eq 'text/plain'
+ expect(response.media_type).to eq 'text/plain'
end
end
end
@@ -55,14 +55,14 @@ RSpec.describe HealthCheckController, :request_store do
get :index
expect(response).to be_successful
- expect(response.content_type).to eq 'text/plain'
+ expect(response.media_type).to eq 'text/plain'
end
it 'supports successful json response' do
get :index, format: :json
expect(response).to be_successful
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
@@ -70,7 +70,7 @@ RSpec.describe HealthCheckController, :request_store do
get :index, format: :xml
expect(response).to be_successful
- expect(response.content_type).to eq 'application/xml'
+ expect(response.media_type).to eq 'application/xml'
expect(xml_response['healthy']).to be true
end
@@ -78,7 +78,7 @@ RSpec.describe HealthCheckController, :request_store do
get :index, params: { checks: 'email' }, format: :json
expect(response).to be_successful
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
end
@@ -102,7 +102,7 @@ RSpec.describe HealthCheckController, :request_store do
get :index
expect(response).to have_gitlab_http_status(:internal_server_error)
- expect(response.content_type).to eq 'text/plain'
+ expect(response.media_type).to eq 'text/plain'
expect(response.body).to include('The server is on fire')
end
@@ -110,7 +110,7 @@ RSpec.describe HealthCheckController, :request_store do
get :index, format: :json
expect(response).to have_gitlab_http_status(:internal_server_error)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
expect(json_response['healthy']).to be false
expect(json_response['message']).to include('The server is on fire')
end
@@ -119,7 +119,7 @@ RSpec.describe HealthCheckController, :request_store do
get :index, format: :xml
expect(response).to have_gitlab_http_status(:internal_server_error)
- expect(response.content_type).to eq 'application/xml'
+ expect(response.media_type).to eq 'application/xml'
expect(xml_response['healthy']).to be false
expect(xml_response['message']).to include('The server is on fire')
end
@@ -128,7 +128,7 @@ RSpec.describe HealthCheckController, :request_store do
get :index, params: { checks: 'email' }, format: :json
expect(response).to have_gitlab_http_status(:internal_server_error)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
expect(json_response['healthy']).to be false
expect(json_response['message']).to include('Email is on fire')
end
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 9ac42cbc3ec..6927df3b1c7 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -101,7 +101,8 @@ RSpec.describe HelpController do
context 'for Markdown formats' do
context 'when requested file exists' do
before do
- expect(File).to receive(:read).and_return(fixture_file('blockquote_fence_after.md'))
+ expect_file_read(File.join(Rails.root, 'doc/ssh/README.md'), content: fixture_file('blockquote_fence_after.md'))
+
get :show, params: { path: 'ssh/README' }, format: :md
end
@@ -213,6 +214,6 @@ RSpec.describe HelpController do
end
def stub_readme(content)
- expect(File).to receive(:read).and_return(content)
+ expect_file_read(Rails.root.join('doc', 'README.md'), content: content)
end
end
diff --git a/spec/controllers/import/bulk_imports_controller_spec.rb b/spec/controllers/import/bulk_imports_controller_spec.rb
index dd850a86227..436daed0af6 100644
--- a/spec/controllers/import/bulk_imports_controller_spec.rb
+++ b/spec/controllers/import/bulk_imports_controller_spec.rb
@@ -57,8 +57,8 @@ RSpec.describe Import::BulkImportsController do
let(:client_response) do
double(
parsed_response: [
- { 'id' => 1, 'full_name' => 'group1', 'full_path' => 'full/path/group1' },
- { 'id' => 2, 'full_name' => 'group2', 'full_path' => 'full/path/group2' }
+ { 'id' => 1, 'full_name' => 'group1', 'full_path' => 'full/path/group1', 'web_url' => 'http://demo.host/full/path/group1' },
+ { 'id' => 2, 'full_name' => 'group2', 'full_path' => 'full/path/group2', 'web_url' => 'http://demo.host/full/path/group1' }
]
)
end
@@ -132,12 +132,27 @@ RSpec.describe Import::BulkImportsController do
end
describe 'POST create' do
+ let(:instance_url) { "http://fake-intance" }
+ let(:pat) { "fake-pat" }
+
+ before do
+ session[:bulk_import_gitlab_access_token] = pat
+ session[:bulk_import_gitlab_url] = instance_url
+ end
+
it 'executes BulkImportService' do
- expect_next_instance_of(BulkImportService) do |service|
+ bulk_import_params = [{ "source_type" => "group_entity",
+ "source_full_path" => "full_path",
+ "destination_name" =>
+ "destination_name",
+ "destination_namespace" => "root" }]
+
+ expect_next_instance_of(
+ BulkImportService, user, bulk_import_params, { url: instance_url, access_token: pat }) do |service|
expect(service).to receive(:execute)
end
- post :create
+ post :create, params: { bulk_import: bulk_import_params }
expect(response).to have_gitlab_http_status(:ok)
end
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index a408d821833..d82fff1f7ae 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -123,26 +123,33 @@ RSpec.describe Import::GithubController do
end
it 'fetches repos using latest github client' do
- expect_next_instance_of(Gitlab::GithubImport::Client) do |client|
- expect(client).to receive(:each_page).with(:repos).and_return([].to_enum)
+ expect_next_instance_of(Octokit::Client) do |client|
+ expect(client).to receive(:repos).and_return([].to_enum)
end
get :status
end
- it 'concatenates list of repos from multiple pages' do
- repo_1 = OpenStruct.new(login: 'emacs', full_name: 'asd/emacs', name: 'emacs', owner: { login: 'owner' })
- repo_2 = OpenStruct.new(login: 'vim', full_name: 'asd/vim', name: 'vim', owner: { login: 'owner' })
- repos = [OpenStruct.new(objects: [repo_1]), OpenStruct.new(objects: [repo_2])].to_enum
+ context 'pagination' do
+ context 'when no page is specified' do
+ it 'requests first page' do
+ expect_next_instance_of(Octokit::Client) do |client|
+ expect(client).to receive(:repos).with(nil, { page: 1, per_page: 25 }).and_return([].to_enum)
+ end
- allow(stub_client).to receive(:each_page).and_return(repos)
+ get :status
+ end
+ end
- get :status, format: :json
+ context 'when page is specified' do
+ it 'requests repos with specified page' do
+ expect_next_instance_of(Octokit::Client) do |client|
+ expect(client).to receive(:repos).with(nil, { page: 2, per_page: 25 }).and_return([].to_enum)
+ end
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.dig('provider_repos').count).to eq(2)
- expect(json_response.dig('provider_repos', 0, 'id')).to eq(repo_1.id)
- expect(json_response.dig('provider_repos', 1, 'id')).to eq(repo_2.id)
+ get :status, params: { page: 2 }
+ end
+ end
end
context 'when filtering' do
@@ -150,6 +157,7 @@ RSpec.describe Import::GithubController do
let(:user_login) { 'user' }
let(:collaborations_subquery) { 'repo:repo1 repo:repo2' }
let(:organizations_subquery) { 'org:org1 org:org2' }
+ let(:search_query) { "test in:name is:public,private user:#{user_login} #{collaborations_subquery} #{organizations_subquery}" }
before do
allow_next_instance_of(Octokit::Client) do |client|
@@ -158,20 +166,56 @@ RSpec.describe Import::GithubController do
end
it 'makes request to github search api' do
- expected_query = "test in:name is:public,private user:#{user_login} #{collaborations_subquery} #{organizations_subquery}"
+ expect_next_instance_of(Octokit::Client) do |client|
+ expect(client).to receive(:user).and_return(double(login: user_login))
+ expect(client).to receive(:search_repositories).with(search_query, { page: 1, per_page: 25 }).and_return({ items: [].to_enum })
+ end
expect_next_instance_of(Gitlab::GithubImport::Client) do |client|
expect(client).to receive(:collaborations_subquery).and_return(collaborations_subquery)
expect(client).to receive(:organizations_subquery).and_return(organizations_subquery)
- expect(client).to receive(:each_page).with(:search_repositories, expected_query).and_return([].to_enum)
end
get :status, params: { filter: filter }, format: :json
end
+ context 'pagination' do
+ context 'when no page is specified' do
+ it 'requests first page' do
+ expect_next_instance_of(Octokit::Client) do |client|
+ expect(client).to receive(:user).and_return(double(login: user_login))
+ expect(client).to receive(:search_repositories).with(search_query, { page: 1, per_page: 25 }).and_return({ items: [].to_enum })
+ end
+
+ expect_next_instance_of(Gitlab::GithubImport::Client) do |client|
+ expect(client).to receive(:collaborations_subquery).and_return(collaborations_subquery)
+ expect(client).to receive(:organizations_subquery).and_return(organizations_subquery)
+ end
+
+ get :status, params: { filter: filter }, format: :json
+ end
+ end
+
+ context 'when page is specified' do
+ it 'requests repos with specified page' do
+ expect_next_instance_of(Octokit::Client) do |client|
+ expect(client).to receive(:user).and_return(double(login: user_login))
+ expect(client).to receive(:search_repositories).with(search_query, { page: 2, per_page: 25 }).and_return({ items: [].to_enum })
+ end
+
+ expect_next_instance_of(Gitlab::GithubImport::Client) do |client|
+ expect(client).to receive(:collaborations_subquery).and_return(collaborations_subquery)
+ expect(client).to receive(:organizations_subquery).and_return(organizations_subquery)
+ end
+
+ get :status, params: { filter: filter, page: 2 }, format: :json
+ end
+ end
+ end
+
context 'when user input contains colons and spaces' do
before do
- stub_client(search_repos_by_name: [])
+ allow(controller).to receive(:client_repos).and_return([])
end
it 'sanitizes user input' do
diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb
deleted file mode 100644
index 0fda111c029..00000000000
--- a/spec/controllers/import/google_code_controller_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Import::GoogleCodeController do
- include ImportSpecHelper
-
- let(:user) { create(:user) }
- let(:dump_file) { fixture_file_upload('spec/fixtures/GoogleCodeProjectHosting.json', 'application/json') }
-
- before do
- sign_in(user)
- end
-
- describe "POST callback" do
- it "stores Google Takeout dump list in session" do
- post :callback, params: { dump_file: dump_file }
-
- expect(session[:google_code_dump]).to be_a(Hash)
- expect(session[:google_code_dump]["kind"]).to eq("projecthosting#user")
- expect(session[:google_code_dump]).to have_key("projects")
- end
- end
-
- describe "GET status" do
- before do
- @repo = OpenStruct.new(name: 'vim')
- stub_client(valid?: true)
- end
-
- it "assigns variables" do
- @project = create(:project, import_type: 'google_code', creator_id: user.id)
- stub_client(repos: [@repo], incompatible_repos: [])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([@repo])
- expect(assigns(:incompatible_repos)).to eq([])
- end
-
- it "does not show already added project" do
- @project = create(:project, import_type: 'google_code', creator_id: user.id, import_source: 'vim')
- stub_client(repos: [@repo], incompatible_repos: [])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([])
- end
-
- it "does not show any invalid projects" do
- stub_client(repos: [], incompatible_repos: [@repo])
-
- get :status
-
- expect(assigns(:repos)).to be_empty
- expect(assigns(:incompatible_repos)).to eq([@repo])
- end
- end
-
- describe "POST create" do
- it_behaves_like 'project import rate limiter'
- end
-end
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index 2d13b942c31..e863f5ef2fc 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -22,43 +22,6 @@ RSpec.describe InvitesController, :snowplow do
end
end
- shared_examples "tracks the 'accepted' event for the invitation reminders experiment" do
- before do
- stub_experiment(invitation_reminders: true)
- allow(Gitlab::Experimentation).to receive(:enabled_for_attribute?).with(:invitation_reminders, member.invite_email).and_return(experimental_group)
- end
-
- context 'when in the control group' do
- let(:experimental_group) { false }
-
- it "tracks the 'accepted' event" do
- request
-
- expect_snowplow_event(
- category: 'Growth::Acquisition::Experiment::InvitationReminders',
- label: md5_member_global_id,
- property: 'control_group',
- action: 'accepted'
- )
- end
- end
-
- context 'when in the experimental group' do
- let(:experimental_group) { true }
-
- it "tracks the 'accepted' event" do
- request
-
- expect_snowplow_event(
- category: 'Growth::Acquisition::Experiment::InvitationReminders',
- label: md5_member_global_id,
- property: 'experimental_group',
- action: 'accepted'
- )
- end
- end
- end
-
describe 'GET #show' do
subject(:request) { get :show, params: params }
@@ -87,7 +50,6 @@ RSpec.describe InvitesController, :snowplow do
expect(flash[:notice]).to be_nil
end
- it_behaves_like "tracks the 'accepted' event for the invitation reminders experiment"
it_behaves_like 'invalid token'
end
@@ -119,7 +81,6 @@ RSpec.describe InvitesController, :snowplow do
subject(:request) { post :accept, params: params }
- it_behaves_like "tracks the 'accepted' event for the invitation reminders experiment"
it_behaves_like 'invalid token'
end
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
index 291d51348e6..edd587389cb 100644
--- a/spec/controllers/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -367,8 +367,8 @@ RSpec.describe OmniauthCallbacksController, type: :controller do
before do
stub_last_request_id(last_request_id)
- stub_omniauth_saml_config({ enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'],
- providers: [saml_config] })
+ stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'],
+ providers: [saml_config])
mock_auth_hash_with_saml_xml('saml', +'my-uid', user.email, mock_saml_response)
request.env['devise.mapping'] = Devise.mappings[:user]
request.env['omniauth.auth'] = Rails.application.env_config['omniauth.auth']
diff --git a/spec/controllers/profiles/gpg_keys_controller_spec.rb b/spec/controllers/profiles/gpg_keys_controller_spec.rb
new file mode 100644
index 00000000000..93f899df484
--- /dev/null
+++ b/spec/controllers/profiles/gpg_keys_controller_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Profiles::GpgKeysController do
+ let(:user) { create(:user, email: GpgHelpers::User1.emails[0]) }
+
+ describe 'POST #create' do
+ before do
+ sign_in(user)
+ end
+
+ it 'creates a new key' do
+ expect do
+ post :create, params: { gpg_key: build(:gpg_key).attributes }
+ end.to change { GpgKey.count }.by(1)
+ end
+ end
+end
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 17964ef4a43..66f6135df1e 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -20,108 +20,4 @@ RSpec.describe Profiles::KeysController do
expect(Key.last.expires_at).to be_like_time(expires_at)
end
end
-
- describe "#get_keys" do
- describe "non existent user" do
- it "does not generally work" do
- get :get_keys, params: { username: 'not-existent' }
-
- expect(response).not_to be_successful
- end
- end
-
- describe "user with no keys" do
- it "does generally work" do
- get :get_keys, params: { username: user.username }
-
- expect(response).to be_successful
- end
-
- it "renders all keys separated with a new line" do
- get :get_keys, params: { username: user.username }
-
- expect(response.body).to eq("")
- end
- it "responds with text/plain content type" do
- get :get_keys, params: { username: user.username }
- expect(response.content_type).to eq("text/plain")
- end
- end
-
- describe "user with keys" do
- let!(:key) { create(:key, user: user) }
- let!(:another_key) { create(:another_key, user: user) }
- let!(:deploy_key) { create(:deploy_key, user: user) }
-
- describe "while signed in" do
- before do
- sign_in(user)
- end
-
- it "does generally work" do
- get :get_keys, params: { username: user.username }
-
- expect(response).to be_successful
- end
-
- it "renders all non deploy keys separated with a new line" do
- get :get_keys, params: { username: user.username }
-
- expect(response.body).not_to eq('')
- expect(response.body).to eq(user.all_ssh_keys.join("\n"))
-
- expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
- expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', ''))
-
- expect(response.body).not_to include(deploy_key.key)
- end
-
- it "does not render the comment of the key" do
- get :get_keys, params: { username: user.username }
- expect(response.body).not_to match(/dummy@gitlab.com/)
- end
-
- it "responds with text/plain content type" do
- get :get_keys, params: { username: user.username }
-
- expect(response.content_type).to eq("text/plain")
- end
- end
-
- describe 'when logged out' do
- before do
- sign_out(user)
- end
-
- it "still does generally work" do
- get :get_keys, params: { username: user.username }
-
- expect(response).to be_successful
- end
-
- it "renders all non deploy keys separated with a new line" do
- get :get_keys, params: { username: user.username }
-
- expect(response.body).not_to eq('')
- expect(response.body).to eq(user.all_ssh_keys.join("\n"))
-
- expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
- expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', ''))
-
- expect(response.body).not_to include(deploy_key.key)
- end
-
- it "does not render the comment of the key" do
- get :get_keys, params: { username: user.username }
- expect(response.body).not_to match(/dummy@gitlab.com/)
- end
-
- it "responds with text/plain content type" do
- get :get_keys, params: { username: user.username }
-
- expect(response.content_type).to eq("text/plain")
- end
- end
- end
- end
end
diff --git a/spec/controllers/projects/alerting/notifications_controller_spec.rb b/spec/controllers/projects/alerting/notifications_controller_spec.rb
index b3d37723ccf..3656cfbcc30 100644
--- a/spec/controllers/projects/alerting/notifications_controller_spec.rb
+++ b/spec/controllers/projects/alerting/notifications_controller_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Projects::Alerting::NotificationsController do
expect(notify_service_class)
.to have_received(:new)
- .with(project, nil, permitted_params)
+ .with(project, permitted_params)
end
end
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index dad932f9cdf..1ed61e0990f 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe Projects::BoardsController do
list_boards
expect(response).to render_template :index
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
context 'with unauthorized user' do
@@ -41,7 +41,7 @@ RSpec.describe Projects::BoardsController do
list_boards
expect(response).to have_gitlab_http_status(:not_found)
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
end
@@ -57,7 +57,7 @@ RSpec.describe Projects::BoardsController do
list_boards
expect(response).to render_template :index
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
end
end
@@ -85,7 +85,7 @@ RSpec.describe Projects::BoardsController do
list_boards format: :json
expect(response).to have_gitlab_http_status(:not_found)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
end
end
end
@@ -127,7 +127,7 @@ RSpec.describe Projects::BoardsController do
expect { read_board board: board }.to change(BoardProjectRecentVisit, :count).by(1)
expect(response).to render_template :show
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
context 'with unauthorized user' do
@@ -141,7 +141,7 @@ RSpec.describe Projects::BoardsController do
read_board board: board
expect(response).to have_gitlab_http_status(:not_found)
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
end
@@ -154,7 +154,7 @@ RSpec.describe Projects::BoardsController do
expect { read_board board: board }.to change(BoardProjectRecentVisit, :count).by(0)
expect(response).to render_template :show
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
end
end
@@ -179,7 +179,7 @@ RSpec.describe Projects::BoardsController do
read_board board: board, format: :json
expect(response).to have_gitlab_http_status(:not_found)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
end
end
end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 625fc5bddda..14a5e7da7d2 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -512,7 +512,8 @@ RSpec.describe Projects::BranchesController do
state: 'all'
}
- expect(controller.instance_variable_get(:@branch_pipeline_statuses)["master"].group).to eq("success")
+ expect(assigns[:branch_pipeline_statuses]["master"].group).to eq("success")
+ expect(assigns[:sort]).to eq('updated_desc')
end
end
@@ -543,8 +544,8 @@ RSpec.describe Projects::BranchesController do
state: 'all'
}
- expect(controller.instance_variable_get(:@branch_pipeline_statuses)["master"].group).to eq("running")
- expect(controller.instance_variable_get(:@branch_pipeline_statuses)["test"].group).to eq("success")
+ expect(assigns[:branch_pipeline_statuses]["master"].group).to eq("running")
+ expect(assigns[:branch_pipeline_statuses]["test"].group).to eq("success")
end
end
@@ -555,10 +556,11 @@ RSpec.describe Projects::BranchesController do
params: {
namespace_id: project.namespace,
project_id: project,
- state: 'all'
+ state: 'stale'
}
- expect(controller.instance_variable_get(:@branch_pipeline_statuses)).to be_blank
+ expect(assigns[:branch_pipeline_statuses]).to be_blank
+ expect(assigns[:sort]).to eq('updated_asc')
end
end
@@ -573,10 +575,12 @@ RSpec.describe Projects::BranchesController do
params: {
namespace_id: project.namespace,
project_id: project,
+ sort: 'name_asc',
state: 'all'
}
expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns[:sort]).to eq('name_asc')
end
end
@@ -635,6 +639,34 @@ RSpec.describe Projects::BranchesController do
expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
end
end
+
+ context 'fetching branches for overview' do
+ before do
+ get :index, format: :html, params: {
+ namespace_id: project.namespace, project_id: project, state: 'overview'
+ }
+ end
+
+ it 'sets active and stale branches' do
+ expect(assigns[:active_branches]).to eq([])
+ expect(assigns[:stale_branches].map(&:name)).to eq(
+ ["feature", "improve/awesome", "merge-test", "markdown", "feature_conflict", "'test'"]
+ )
+ end
+
+ context 'branch_list_keyset_pagination is disabled' do
+ before do
+ stub_feature_flags(branch_list_keyset_pagination: false)
+ end
+
+ it 'sets active and stale branches' do
+ expect(assigns[:active_branches]).to eq([])
+ expect(assigns[:stale_branches].map(&:name)).to eq(
+ ["feature", "improve/awesome", "merge-test", "markdown", "feature_conflict", "'test'"]
+ )
+ end
+ end
+ end
end
describe 'GET diverging_commit_counts' do
diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb
index c4e040b0287..d778739be38 100644
--- a/spec/controllers/projects/ci/lints_controller_spec.rb
+++ b/spec/controllers/projects/ci/lints_controller_spec.rb
@@ -82,7 +82,7 @@ RSpec.describe Projects::Ci::LintsController do
end
it 'renders json' do
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
expect(parsed_body).to include('errors', 'warnings', 'jobs', 'valid')
expect(parsed_body).to match_schema('entities/lint_result_entity')
end
diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb
index b50814b4790..cc6170252c1 100644
--- a/spec/controllers/projects/clusters/applications_controller_spec.rb
+++ b/spec/controllers/projects/clusters/applications_controller_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Projects::Clusters::ApplicationsController do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
- let(:application) { 'helm' }
+ let(:application) { 'ingress' }
let(:params) { { application: application, id: cluster.id } }
describe 'functionality' do
@@ -48,7 +48,7 @@ RSpec.describe Projects::Clusters::ApplicationsController do
expect { subject }.to change { current_application.count }
expect(response).to have_gitlab_http_status(:no_content)
- expect(cluster.application_helm).to be_scheduled
+ expect(cluster.application_ingress).to be_scheduled
end
context 'when cluster do not exists' do
@@ -72,7 +72,7 @@ RSpec.describe Projects::Clusters::ApplicationsController do
context 'when application is already installing' do
before do
- create(:clusters_applications_helm, :installing, cluster: cluster)
+ create(:clusters_applications_ingress, :installing, cluster: cluster)
end
it 'returns 400' do
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 52cd6869b04..dd3440f7660 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -500,7 +500,7 @@ RSpec.describe Projects::ClustersController do
expect { post_create_aws }.not_to change { Clusters::Cluster.count }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
- expect(response.content_type).to eq('application/json')
+ expect(response.media_type).to eq('application/json')
expect(response.body).to include('is invalid')
end
end
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 557002acbc0..4cf77fde3a1 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -80,7 +80,7 @@ RSpec.describe Projects::CommitsController do
it "renders as atom" do
expect(response).to be_successful
- expect(response.content_type).to eq('application/atom+xml')
+ expect(response.media_type).to eq('application/atom+xml')
end
it 'renders summary with type=html' do
@@ -105,7 +105,7 @@ RSpec.describe Projects::CommitsController do
it "renders as HTML" do
expect(response).to be_successful
- expect(response.content_type).to eq('text/html')
+ expect(response.media_type).to eq('text/html')
end
end
end
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 24c2d568d9a..ccd213fdffa 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -32,41 +32,5 @@ RSpec.describe Projects::CycleAnalyticsController do
end
end
- describe 'value stream analytics not set up flag' do
- context 'with no data' do
- it 'is true' do
- get(:show,
- params: {
- namespace_id: project.namespace,
- project_id: project
- })
-
- expect(response).to be_successful
- expect(assigns(:cycle_analytics_no_data)).to eq(true)
- end
- end
-
- context 'with data' do
- before do
- issue = create(:issue, project: project, created_at: 4.days.ago)
- milestone = create(:milestone, project: project, created_at: 5.days.ago)
- issue.update(milestone: milestone)
-
- create_merge_request_closing_issue(user, project, issue)
- end
-
- it 'is false' do
- get(:show,
- params: {
- namespace_id: project.namespace,
- project_id: project
- })
-
- expect(response).to be_successful
- expect(assigns(:cycle_analytics_no_data)).to eq(false)
- end
- end
- end
-
include_examples GracefulTimeoutHandling
end
diff --git a/spec/controllers/projects/feature_flags_controller_spec.rb b/spec/controllers/projects/feature_flags_controller_spec.rb
index 1473ec95192..d5fc80bd5a7 100644
--- a/spec/controllers/projects/feature_flags_controller_spec.rb
+++ b/spec/controllers/projects/feature_flags_controller_spec.rb
@@ -217,15 +217,6 @@ RSpec.describe Projects::FeatureFlagsController do
expect(json_response['feature_flags'].count).to eq(3)
end
-
- it 'returns only version 1 flags when new version flags are disabled' do
- stub_feature_flags(feature_flags_new_version: false)
-
- subject
-
- expected = [feature_flag_active.name, feature_flag_inactive.name].sort
- expect(json_response['feature_flags'].map { |f| f['name'] }.sort).to eq(expected)
- end
end
end
@@ -283,24 +274,6 @@ RSpec.describe Projects::FeatureFlagsController do
expect(json_response['name']).to eq(other_feature_flag.name)
end
- it 'routes based on iid when new version flags are disabled' do
- stub_feature_flags(feature_flags_new_version: false)
- other_project = create(:project)
- other_project.add_developer(user)
- other_feature_flag = create(:operations_feature_flag, project: other_project,
- name: 'other_flag')
- params = {
- namespace_id: other_project.namespace,
- project_id: other_project,
- iid: other_feature_flag.iid
- }
-
- get(:show, params: params, format: :json)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['name']).to eq(other_feature_flag.name)
- end
-
context 'when feature flag is not found' do
let!(:feature_flag) { }
@@ -386,14 +359,6 @@ RSpec.describe Projects::FeatureFlagsController do
expect(json_response['version']).to eq('new_version_flag')
end
- it 'returns a 404 when new version flags are disabled' do
- stub_feature_flags(feature_flags_new_version: false)
-
- subject
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
-
it 'returns strategies ordered by id' do
first_strategy = create(:operations_strategy, feature_flag: new_version_feature_flag)
second_strategy = create(:operations_strategy, feature_flag: new_version_feature_flag)
@@ -791,54 +756,6 @@ RSpec.describe Projects::FeatureFlagsController do
expect(Operations::FeatureFlag.count).to eq(0)
end
end
-
- context 'when version 2 flags are disabled' do
- context 'and attempting to create a version 2 flag' do
- let(:params) do
- {
- namespace_id: project.namespace,
- project_id: project,
- operations_feature_flag: {
- name: 'my_feature_flag',
- active: true,
- version: 'new_version_flag'
- }
- }
- end
-
- it 'returns a 400' do
- stub_feature_flags(feature_flags_new_version: false)
-
- subject
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(Operations::FeatureFlag.count).to eq(0)
- end
- end
-
- context 'and attempting to create a version 1 flag' do
- let(:params) do
- {
- namespace_id: project.namespace,
- project_id: project,
- operations_feature_flag: {
- name: 'my_feature_flag',
- active: true
- }
- }
- end
-
- it 'creates the flag' do
- stub_feature_flags(feature_flags_new_version: false)
-
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(Operations::FeatureFlag.count).to eq(1)
- expect(json_response['version']).to eq('legacy_flag')
- end
- end
- end
end
describe 'DELETE destroy.json' do
@@ -913,15 +830,6 @@ RSpec.describe Projects::FeatureFlagsController do
it 'deletes the flag' do
expect { subject }.to change { Operations::FeatureFlag.count }.by(-1)
end
-
- context 'when new version flags are disabled' do
- it 'returns a 404' do
- stub_feature_flags(feature_flags_new_version: false)
-
- expect { subject }.not_to change { Operations::FeatureFlag.count }
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
end
end
@@ -1610,15 +1518,6 @@ RSpec.describe Projects::FeatureFlagsController do
expect(json_response['strategies'].first['scopes']).to eq([])
end
- it 'does not update the flag if version 2 flags are disabled' do
- stub_feature_flags(feature_flags_new_version: false)
-
- put_request(new_version_flag, { name: 'some-other-name' })
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(new_version_flag.reload.name).to eq('new-feature')
- end
-
it 'updates the flag when legacy feature flags are set to be read only' do
stub_feature_flags(feature_flags_legacy_read_only: true)
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 26e1842468b..12c8c84dd77 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -62,6 +62,56 @@ RSpec.describe Projects::IssuesController do
expect(response).to have_gitlab_http_status(:moved_permanently)
end
end
+
+ describe 'the null hypothesis experiment', :snowplow do
+ it 'defines the expected before actions' do
+ expect(controller).to use_before_action(:run_null_hypothesis_experiment)
+ end
+
+ context 'when rolled out to 100%' do
+ it 'assigns the candidate experience and tracks the event' do
+ get :index, params: { namespace_id: project.namespace, project_id: project }
+
+ expect_snowplow_event(
+ category: 'null_hypothesis',
+ action: 'index',
+ context: [{
+ schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/0-3-0',
+ data: { variant: 'candidate', experiment: 'null_hypothesis', key: anything }
+ }]
+ )
+ end
+ end
+
+ context 'when not rolled out' do
+ before do
+ stub_feature_flags(null_hypothesis: false)
+ end
+
+ it 'assigns the control experience and tracks the event' do
+ get :index, params: { namespace_id: project.namespace, project_id: project }
+
+ expect_snowplow_event(
+ category: 'null_hypothesis',
+ action: 'index',
+ context: [{
+ schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/0-3-0',
+ data: { variant: 'control', experiment: 'null_hypothesis', key: anything }
+ }]
+ )
+ end
+ end
+
+ context 'when gitlab_experiments is disabled' do
+ it 'does not run the experiment at all' do
+ stub_feature_flags(gitlab_experiments: false)
+
+ expect(controller).not_to receive(:run_null_hypothesis_experiment)
+
+ get :index, params: { namespace_id: project.namespace, project_id: project }
+ end
+ end
+ end
end
context 'internal issue tracker' do
@@ -1128,12 +1178,12 @@ RSpec.describe Projects::IssuesController do
{ merge_request_to_resolve_discussions_of: merge_request.iid }
end
- def post_issue(issue_params, other_params: {})
+ def post_issue(other_params: {}, **issue_params)
post :create, params: { namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_to_resolve_discussions_of: merge_request.iid }.merge(other_params)
end
it 'creates an issue for the project' do
- expect { post_issue({ title: 'Hello' }) }.to change { project.issues.reload.size }.by(1)
+ expect { post_issue(title: 'Hello') }.to change { project.issues.reload.size }.by(1)
end
it "doesn't overwrite given params" do
@@ -1157,7 +1207,7 @@ RSpec.describe Projects::IssuesController do
describe "resolving a single discussion" do
before do
- post_issue({ title: 'Hello' }, other_params: { discussion_to_resolve: discussion.id })
+ post_issue(title: 'Hello', other_params: { discussion_to_resolve: discussion.id })
end
it 'resolves a single discussion' do
discussion.first_note.reload
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 80cb16966e5..3309b15b276 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -15,6 +15,54 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
describe 'GET index' do
+ describe 'pushing tracking_data to Gon' do
+ before do
+ stub_experiment(jobs_empty_state: experiment_active)
+ stub_experiment_for_subject(jobs_empty_state: in_experiment_group)
+
+ get_index
+ end
+
+ context 'when experiment not active' do
+ let(:experiment_active) { false }
+ let(:in_experiment_group) { false }
+
+ it 'does not push tracking_data to Gon' do
+ expect(Gon.tracking_data).to be_nil
+ end
+ end
+
+ context 'when experiment active and user in control group' do
+ let(:experiment_active) { true }
+ let(:in_experiment_group) { false }
+
+ it 'pushes tracking_data to Gon' do
+ expect(Gon.tracking_data).to match(
+ {
+ category: 'Growth::Activation::Experiment::JobsEmptyState',
+ action: 'click_button',
+ label: anything,
+ property: 'control_group'
+ }
+ )
+ end
+ end
+
+ context 'when experiment active and user in experimental group' do
+ let(:experiment_active) { true }
+ let(:in_experiment_group) { true }
+
+ it 'pushes tracking_data to gon' do
+ expect(Gon.tracking_data).to match(
+ category: 'Growth::Activation::Experiment::JobsEmptyState',
+ action: 'click_button',
+ label: anything,
+ property: 'experimental_group'
+ )
+ end
+ end
+ end
+
context 'when scope is pending' do
before do
create(:ci_build, :pending, pipeline: pipeline)
@@ -113,11 +161,11 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'when requesting HTML' do
context 'when job exists' do
- before do
- get_show(id: job.id)
- end
+ let(:extra_params) { { id: job.id } }
it 'has a job' do
+ get_show(**extra_params)
+
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:build).id).to eq(job.id)
end
@@ -599,6 +647,46 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(json_response['total']).to be_present
expect(json_response['lines'].count).to be_positive
end
+
+ context 'when CI_DEBUG_TRACE enabled' do
+ let!(:variable) { create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true') }
+
+ context 'with proper permissions on a project' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ it 'returns response ok' do
+ get_trace
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'without proper permissions for debug logging' do
+ before do
+ project.add_guest(user)
+ sign_in(user)
+ end
+
+ it 'returns response forbidden' do
+ get_trace
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'with restrict_access_to_build_debug_mode feature disabled' do
+ before do
+ stub_feature_flags(restrict_access_to_build_debug_mode: false)
+ end
+
+ it 'returns response forbidden' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
end
context 'when job has a trace' do
@@ -806,18 +894,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(job.reload).to be_pending
end
-
- context 'when FF ci_manual_bridges is disabled' do
- before do
- stub_feature_flags(ci_manual_bridges: false)
- end
-
- it 'returns 404' do
- post_play
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
end
end
@@ -1027,7 +1103,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
}
end
- context "when job has a trace artifact" do
+ context 'when job has a trace artifact' do
let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
it "sets #{Gitlab::Workhorse::DETECT_HEADER} header" do
@@ -1038,6 +1114,62 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response.body).to eq(job.job_artifacts_trace.open.read)
expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
+
+ context 'when CI_DEBUG_TRACE enabled' do
+ before do
+ create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true')
+ end
+
+ context 'with proper permissions for debug logging on a project' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ it 'returns response ok' do
+ response = subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'with restrict_access_to_build_debug_mode feature disabled' do
+ before do
+ stub_feature_flags(restrict_access_to_build_debug_mode: false)
+ end
+
+ it 'returns response ok' do
+ response = subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'without proper permissions for debug logging on a project' do
+ before do
+ project.add_reporter(user)
+ sign_in(user)
+ end
+
+ it 'returns response forbidden' do
+ response = subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'with restrict_access_to_build_debug_mode feature disabled' do
+ before do
+ stub_feature_flags(restrict_access_to_build_debug_mode: false)
+ end
+
+ it 'returns response ok' do
+ response = subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
end
context "when job has a trace file" do
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index bda1f1a3b1c..f4f0a9f8108 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -74,6 +74,8 @@ RSpec.describe Projects::MergeRequests::DiffsController do
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
before do
+ stub_feature_flags(diffs_gradual_load: false)
+
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index f159f0e6099..cf8b4c564c4 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -1498,6 +1498,121 @@ RSpec.describe Projects::MergeRequestsController do
end
end
+ describe 'GET codequality_reports' do
+ let_it_be(:merge_request) do
+ create(:merge_request,
+ :with_diffs,
+ :with_merge_request_pipeline,
+ target_project: project,
+ source_project: project
+ )
+ end
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ :success,
+ project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+ end
+
+ before do
+ allow_any_instance_of(MergeRequest)
+ .to receive(:compare_codequality_reports)
+ .and_return(codequality_comparison)
+
+ allow_any_instance_of(MergeRequest)
+ .to receive(:actual_head_pipeline)
+ .and_return(pipeline)
+ end
+
+ subject do
+ get :codequality_reports, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid
+ },
+ format: :json
+ end
+
+ context 'permissions on a public project with private CI/CD' do
+ let(:project) { project_public_with_private_builds }
+ let(:codequality_comparison) { { status: :parsed, data: { summary: 1 } } }
+
+ context 'while signed out' do
+ before do
+ sign_out(user)
+ end
+
+ it 'responds with a 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to be_blank
+ end
+ end
+
+ context 'while signed in as an unrelated user' do
+ before do
+ sign_in(create(:user))
+ end
+
+ it 'responds with a 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to be_blank
+ end
+ end
+ end
+
+ context 'when pipeline has jobs with codequality reports' do
+ before do
+ allow_any_instance_of(MergeRequest)
+ .to receive(:has_codequality_reports?)
+ .and_return(true)
+ end
+
+ context 'when processing codequality reports is in progress' do
+ let(:codequality_comparison) { { status: :parsing } }
+
+ it 'sends polling interval' do
+ expect(Gitlab::PollingInterval).to receive(:set_header)
+
+ subject
+ end
+
+ it 'returns 204 HTTP status' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
+ context 'when processing codequality reports is completed' do
+ let(:codequality_comparison) { { status: :parsed, data: { summary: 1 } } }
+
+ it 'returns codequality reports' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq({ 'summary' => 1 })
+ end
+ end
+ end
+
+ context 'when pipeline has job without a codequality report' do
+ let(:codequality_comparison) { { status: :error, status_reason: 'no codequality report' } }
+
+ it 'returns a 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response).to eq({ 'status_reason' => 'no codequality report' })
+ end
+ end
+ end
+
describe 'POST remove_wip' do
before do
merge_request.title = merge_request.wip_title
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 9e5d41b1075..b93f1b41a7e 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -33,14 +33,14 @@ RSpec.describe Projects::MilestonesController do
view_milestone
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
it 'returns milestone json' do
view_milestone format: :json
expect(response).to have_gitlab_http_status(:not_found)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
end
end
@@ -189,7 +189,7 @@ RSpec.describe Projects::MilestonesController do
get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
expect(json_response['html']).not_to include(label.title)
end
@@ -200,7 +200,7 @@ RSpec.describe Projects::MilestonesController do
get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
expect(json_response['html']).to include(label.title)
end
@@ -262,7 +262,7 @@ RSpec.describe Projects::MilestonesController do
get :participants, params: params
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
expect(json_response['html']).to include(issue_assignee.name)
end
end
@@ -277,7 +277,7 @@ RSpec.describe Projects::MilestonesController do
get :participants, params: params
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
expect(json_response['html']).not_to include(issue_assignee.name)
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index d76432f71b3..e96113c0133 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -426,7 +426,7 @@ RSpec.describe Projects::NotesController do
let(:note_text) { "/award :thumbsup:\n/estimate 1d\n/spend 3h" }
let(:extra_request_params) { { format: :json } }
- it 'includes changes in commands_changes ' do
+ it 'includes changes in commands_changes' do
create!
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 0720124ea57..e1405660ccb 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -1149,11 +1149,17 @@ RSpec.describe Projects::PipelinesController do
end
end
- describe 'GET config_variables.json' do
+ describe 'GET config_variables.json', :use_clean_rails_memory_store_caching do
+ include ReactiveCachingHelpers
+
let(:result) { YAML.dump(ci_config) }
+ let(:service) { Ci::ListConfigVariablesService.new(project, user) }
before do
stub_gitlab_ci_yml_for_sha(sha, result)
+ allow(Ci::ListConfigVariablesService)
+ .to receive(:new)
+ .and_return(service)
end
context 'when sending a valid sha' do
@@ -1170,6 +1176,10 @@ RSpec.describe Projects::PipelinesController do
}
end
+ before do
+ synchronous_reactive_cache(service)
+ end
+
it 'returns variable list' do
get_config_variables
@@ -1182,6 +1192,10 @@ RSpec.describe Projects::PipelinesController do
let(:sha) { 'invalid-sha' }
let(:ci_config) { nil }
+ before do
+ synchronous_reactive_cache(service)
+ end
+
it 'returns empty json' do
get_config_variables
@@ -1204,6 +1218,10 @@ RSpec.describe Projects::PipelinesController do
}
end
+ before do
+ synchronous_reactive_cache(service)
+ end
+
it 'returns empty result' do
get_config_variables
@@ -1212,6 +1230,27 @@ RSpec.describe Projects::PipelinesController do
end
end
+ context 'when the cache is empty' do
+ let(:sha) { 'master' }
+ let(:ci_config) do
+ {
+ variables: {
+ KEY1: { value: 'val 1', description: 'description 1' }
+ },
+ test: {
+ stage: 'test',
+ script: 'echo'
+ }
+ }
+ end
+
+ it 'returns no content' do
+ get_config_variables
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
private
def stub_gitlab_ci_yml_for_sha(sha, result)
diff --git a/spec/controllers/projects/prometheus/alerts_controller_spec.rb b/spec/controllers/projects/prometheus/alerts_controller_spec.rb
index cbd599506df..46de8aa4baf 100644
--- a/spec/controllers/projects/prometheus/alerts_controller_spec.rb
+++ b/spec/controllers/projects/prometheus/alerts_controller_spec.rb
@@ -168,7 +168,7 @@ RSpec.describe Projects::Prometheus::AlertsController do
expect(Projects::Prometheus::Alerts::NotifyService)
.to receive(:new)
- .with(project, nil, duck_type(:permitted?))
+ .with(project, duck_type(:permitted?))
.and_return(notify_service)
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 43cf1a16051..dfe7ba34e6d 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -5,11 +5,11 @@ require 'spec_helper'
RSpec.describe Projects::RawController do
include RepoHelpers
- let(:project) { create(:project, :public, :repository) }
+ let_it_be(:project) { create(:project, :public, :repository) }
let(:inline) { nil }
describe 'GET #show' do
- subject do
+ def get_show
get(:show,
params: {
namespace_id: project.namespace,
@@ -19,6 +19,18 @@ RSpec.describe Projects::RawController do
})
end
+ subject { get_show }
+
+ shared_examples 'single Gitaly request' do
+ it 'makes a single Gitaly request', :request_store, :clean_gitlab_redis_cache do
+ # Warm up to populate repository cache
+ get_show
+ RequestStore.clear!
+
+ expect { get_show }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
+
context 'regular filename' do
let(:filepath) { 'master/README.md' }
@@ -33,6 +45,7 @@ RSpec.describe Projects::RawController do
it_behaves_like 'project cache control headers'
it_behaves_like 'content disposition headers'
+ include_examples 'single Gitaly request'
end
context 'image header' do
@@ -48,6 +61,7 @@ RSpec.describe Projects::RawController do
it_behaves_like 'project cache control headers'
it_behaves_like 'content disposition headers'
+ include_examples 'single Gitaly request'
end
context 'with LFS files' do
@@ -56,6 +70,7 @@ RSpec.describe Projects::RawController do
it_behaves_like 'a controller that can serve LFS files'
it_behaves_like 'project cache control headers'
+ include_examples 'single Gitaly request'
end
context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_cache do
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 07fb03b39c6..c1f1373ddc2 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -83,7 +83,7 @@ RSpec.describe Projects::ReleasesController do
let(:format) { :html }
it 'returns a text/html content_type' do
- expect(response.content_type).to eq 'text/html'
+ expect(response.media_type).to eq 'text/html'
end
it_behaves_like 'common access controls'
@@ -101,7 +101,7 @@ RSpec.describe Projects::ReleasesController do
let(:format) { :json }
it 'returns an application/json content_type' do
- expect(response.content_type).to eq 'application/json'
+ expect(response.media_type).to eq 'application/json'
end
it "returns the project's releases as JSON, ordered by released_at" do
diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb
index 2443a823070..d63d88f8283 100644
--- a/spec/controllers/projects/runners_controller_spec.rb
+++ b/spec/controllers/projects/runners_controller_spec.rb
@@ -78,40 +78,84 @@ RSpec.describe Projects::RunnersController do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
- it 'toggles shared_runners_enabled when the group allows shared runners' do
- project.update!(shared_runners_enabled: true)
+ context 'without feature flag' do
+ before do
+ stub_feature_flags(vueify_shared_runners_toggle: false)
+ end
- post :toggle_shared_runners, params: params
+ it 'toggles shared_runners_enabled when the group allows shared runners' do
+ project.update!(shared_runners_enabled: true)
- project.reload
+ post :toggle_shared_runners, params: params
- expect(response).to have_gitlab_http_status(:found)
- expect(project.shared_runners_enabled).to eq(false)
- end
+ project.reload
- it 'toggles shared_runners_enabled when the group disallows shared runners but allows overrides' do
- group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: true)
- project.update!(shared_runners_enabled: false)
+ expect(response).to have_gitlab_http_status(:found)
+ expect(project.shared_runners_enabled).to eq(false)
+ end
- post :toggle_shared_runners, params: params
+ it 'toggles shared_runners_enabled when the group disallows shared runners but allows overrides' do
+ group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: true)
+ project.update!(shared_runners_enabled: false)
- project.reload
+ post :toggle_shared_runners, params: params
- expect(response).to have_gitlab_http_status(:found)
- expect(project.shared_runners_enabled).to eq(true)
+ project.reload
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(project.shared_runners_enabled).to eq(true)
+ end
+
+ it 'does not enable if the group disallows shared runners' do
+ group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: false)
+ project.update!(shared_runners_enabled: false)
+
+ post :toggle_shared_runners, params: params
+
+ project.reload
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(project.shared_runners_enabled).to eq(false)
+ expect(flash[:alert]).to eq('Cannot enable shared runners because parent group does not allow it')
+ end
end
- it 'does not enable if the group disallows shared runners' do
- group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: false)
- project.update!(shared_runners_enabled: false)
+ context 'with feature flag: vueify_shared_runners_toggle' do
+ it 'toggles shared_runners_enabled when the group allows shared runners' do
+ project.update!(shared_runners_enabled: true)
- post :toggle_shared_runners, params: params
+ post :toggle_shared_runners, params: params
- project.reload
+ project.reload
- expect(response).to have_gitlab_http_status(:found)
- expect(project.shared_runners_enabled).to eq(false)
- expect(flash[:alert]).to eq("Cannot enable shared runners because parent group does not allow it")
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.shared_runners_enabled).to eq(false)
+ end
+
+ it 'toggles shared_runners_enabled when the group disallows shared runners but allows overrides' do
+ group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: true)
+ project.update!(shared_runners_enabled: false)
+
+ post :toggle_shared_runners, params: params
+
+ project.reload
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.shared_runners_enabled).to eq(true)
+ end
+
+ it 'does not enable if the group disallows shared runners' do
+ group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: false)
+ project.update!(shared_runners_enabled: false)
+
+ post :toggle_shared_runners, params: params
+
+ project.reload
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(project.shared_runners_enabled).to eq(false)
+ expect(json_response['error']).to eq('Cannot enable shared runners because parent group does not allow it')
+ end
end
end
end
diff --git a/spec/controllers/projects/static_site_editor_controller_spec.rb b/spec/controllers/projects/static_site_editor_controller_spec.rb
index 867b2b51039..b563f3b667f 100644
--- a/spec/controllers/projects/static_site_editor_controller_spec.rb
+++ b/spec/controllers/projects/static_site_editor_controller_spec.rb
@@ -7,6 +7,21 @@ RSpec.describe Projects::StaticSiteEditorController do
let_it_be(:user) { create(:user) }
let(:data) { { key: 'value' } }
+ describe 'GET index' do
+ let(:default_params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project
+ }
+ end
+
+ it 'responds with 404 page' do
+ get :index, params: default_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
describe 'GET show' do
render_views
diff --git a/spec/controllers/projects/terraform_controller_spec.rb b/spec/controllers/projects/terraform_controller_spec.rb
index 1978b9494fa..73f0a5b26fb 100644
--- a/spec/controllers/projects/terraform_controller_spec.rb
+++ b/spec/controllers/projects/terraform_controller_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Projects::TerraformController do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :public) }
describe 'GET index' do
subject { get :index, params: { namespace_id: project.namespace, project_id: project } }
@@ -34,5 +34,15 @@ RSpec.describe Projects::TerraformController do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+ context 'when no user is present' do
+ before do
+ subject
+ end
+
+ it 'shows 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 012a98f433e..bd7ef3db8b6 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -6,15 +6,15 @@ RSpec.describe ProjectsController do
include ExternalAuthorizationServiceHelpers
include ProjectForksHelper
- let(:project) { create(:project, service_desk_enabled: false) }
- let(:public_project) { create(:project, :public) }
- let(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, service_desk_enabled: false) }
+ let_it_be(:public_project) { create(:project, :public) }
+ let_it_be(:user) { create(:user) }
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
describe 'GET new' do
context 'with an authenticated user' do
- let(:group) { create(:group) }
+ let_it_be(:group) { create(:group) }
before do
sign_in(user)
@@ -41,27 +41,6 @@ RSpec.describe ProjectsController do
end
end
end
-
- context 'with the new_create_project_ui experiment enabled and the user is part of the control group' do
- before do
- stub_experiment(new_create_project_ui: true)
- stub_experiment_for_user(new_create_project_ui: false)
- allow_any_instance_of(described_class).to receive(:experimentation_subject_id).and_return('uuid')
- end
-
- it 'passes the right tracking parameters to the frontend' do
- get(:new)
-
- expect(Gon.tracking_data).to eq(
- {
- category: 'Manage::Import::Experiment::NewCreateProjectUi',
- action: 'click_tab',
- label: 'uuid',
- property: 'control_group'
- }
- )
- end
- end
end
end
@@ -89,7 +68,7 @@ RSpec.describe ProjectsController do
include DesignManagementTestHelpers
render_views
- let(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
+ let_it_be(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
before do
enable_design_management
@@ -248,10 +227,12 @@ RSpec.describe ProjectsController do
end
context "project with empty repo" do
- let(:empty_project) { create(:project_empty_repo, :public) }
+ let_it_be(:empty_project) { create(:project_empty_repo, :public) }
before do
sign_in(user)
+
+ allow(controller).to receive(:record_experiment_user).with(:invite_members_empty_project_version_a)
end
User.project_views.keys.each do |project_view|
@@ -262,15 +243,16 @@ RSpec.describe ProjectsController do
get :show, params: { namespace_id: empty_project.namespace, id: empty_project }
end
- it "renders the empty project view" do
+ it "renders the empty project view and records the experiment user", :aggregate_failures do
expect(response).to render_template('empty')
+ expect(controller).to have_received(:record_experiment_user).with(:invite_members_empty_project_version_a)
end
end
end
end
context "project with broken repo" do
- let(:empty_project) { create(:project_broken_repo, :public) }
+ let_it_be(:empty_project) { create(:project_broken_repo, :public) }
before do
sign_in(user)
@@ -294,15 +276,20 @@ RSpec.describe ProjectsController do
end
context "rendering default project view" do
- let(:public_project) { create(:project, :public, :repository) }
+ let_it_be(:public_project) { create(:project, :public, :repository) }
render_views
+ def get_show
+ get :show, params: { namespace_id: public_project.namespace, id: public_project }
+ end
+
it "renders the activity view" do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('activity')
- get :show, params: { namespace_id: public_project.namespace, id: public_project }
+ get_show
+
expect(response).to render_template('_activity')
end
@@ -310,7 +297,8 @@ RSpec.describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('files')
- get :show, params: { namespace_id: public_project.namespace, id: public_project }
+ get_show
+
expect(response).to render_template('_files')
end
@@ -318,9 +306,18 @@ RSpec.describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('readme')
- get :show, params: { namespace_id: public_project.namespace, id: public_project }
+ get_show
+
expect(response).to render_template('_readme')
end
+
+ it 'does not make Gitaly requests', :request_store, :clean_gitlab_redis_cache do
+ # Warm up to populate repository cache
+ get_show
+ RequestStore.clear!
+
+ expect { get_show }.not_to change { Gitlab::GitalyClient.get_request_count }
+ end
end
context "when the url contains .atom" do
@@ -403,8 +400,8 @@ RSpec.describe ProjectsController do
end
describe 'POST #archive' do
- let(:group) { create(:group) }
- let(:project) { create(:project, group: group) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
before do
sign_in(user)
@@ -451,8 +448,8 @@ RSpec.describe ProjectsController do
end
describe 'POST #unarchive' do
- let(:group) { create(:group) }
- let(:project) { create(:project, :archived, group: group) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :archived, group: group) }
before do
sign_in(user)
@@ -499,8 +496,8 @@ RSpec.describe ProjectsController do
end
describe '#housekeeping' do
- let(:group) { create(:group) }
- let(:project) { create(:project, group: group) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
let(:housekeeping) { Projects::HousekeepingService.new(project) }
context 'when authenticated as owner' do
@@ -672,13 +669,13 @@ RSpec.describe ProjectsController do
end
context 'hashed storage' do
- let(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
it_behaves_like 'updating a project'
end
context 'legacy storage' do
- let(:project) { create(:project, :repository, :legacy_storage) }
+ let_it_be(:project) { create(:project, :repository, :legacy_storage) }
it_behaves_like 'updating a project'
end
@@ -713,14 +710,47 @@ RSpec.describe ProjectsController do
end
end
end
+
+ context 'when updating boolean values on project_settings' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:boolean_value, :result) do
+ '1' | true
+ '0' | false
+ 1 | true
+ 0 | false
+ true | true
+ false | false
+ end
+
+ with_them do
+ it 'updates project settings attributes accordingly' do
+ put :update, params: {
+ namespace_id: project.namespace,
+ id: project.path,
+ project: {
+ project_setting_attributes: {
+ show_default_award_emojis: boolean_value,
+ allow_editing_commit_messages: boolean_value
+ }
+ }
+ }
+
+ project.reload
+
+ expect(project.show_default_award_emojis?).to eq(result)
+ expect(project.allow_editing_commit_messages?).to eq(result)
+ end
+ end
+ end
end
describe '#transfer', :enable_admin_mode do
render_views
- let(:project) { create(:project, :repository) }
- let(:admin) { create(:admin) }
- let(:new_namespace) { create(:namespace) }
+ let_it_be(:project, reload: true) { create(:project, :repository) }
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:new_namespace) { create(:namespace) }
it 'updates namespace' do
sign_in(admin)
@@ -764,7 +794,7 @@ RSpec.describe ProjectsController do
end
describe "#destroy", :enable_admin_mode do
- let(:admin) { create(:admin) }
+ let_it_be(:admin) { create(:admin) }
it "redirects to the dashboard", :sidekiq_might_not_need_inline do
controller.instance_variable_set(:@project, project)
@@ -944,7 +974,7 @@ RSpec.describe ProjectsController do
end
describe "GET refs" do
- let(:project) { create(:project, :public, :repository) }
+ let_it_be(:project) { create(:project, :public, :repository) }
it 'gets a list of branches and tags' do
get :refs, params: { namespace_id: project.namespace, id: project, sort: 'updated_desc' }
@@ -1016,7 +1046,7 @@ RSpec.describe ProjectsController do
end
context 'state filter on references' do
- let(:issue) { create(:issue, :closed, project: public_project) }
+ let_it_be(:issue) { create(:issue, :closed, project: public_project) }
let(:merge_request) { create(:merge_request, :closed, target_project: public_project) }
it 'renders JSON body with state filter for issues' do
@@ -1339,7 +1369,7 @@ RSpec.describe ProjectsController do
end
context 'private project with token authentication' do
- let(:private_project) { create(:project, :private) }
+ let_it_be(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user', :show, :atom, ignore_incrementing: true do
before do
@@ -1351,7 +1381,7 @@ RSpec.describe ProjectsController do
end
context 'public project with token authentication' do
- let(:public_project) { create(:project, :public) }
+ let_it_be(:public_project) { create(:project, :public) }
it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
diff --git a/spec/controllers/registrations/experience_levels_controller_spec.rb b/spec/controllers/registrations/experience_levels_controller_spec.rb
index 4be67f29107..015daba8682 100644
--- a/spec/controllers/registrations/experience_levels_controller_spec.rb
+++ b/spec/controllers/registrations/experience_levels_controller_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Registrations::ExperienceLevelsController do
context 'with an authenticated user' do
before do
sign_in(user)
- stub_experiment_for_user(onboarding_issues: true)
+ stub_experiment_for_subject(onboarding_issues: true)
end
it { is_expected.to have_gitlab_http_status(:ok) }
@@ -28,7 +28,7 @@ RSpec.describe Registrations::ExperienceLevelsController do
context 'when not part of the onboarding issues experiment' do
before do
- stub_experiment_for_user(onboarding_issues: false)
+ stub_experiment_for_subject(onboarding_issues: false)
end
it { is_expected.to have_gitlab_http_status(:not_found) }
@@ -47,12 +47,12 @@ RSpec.describe Registrations::ExperienceLevelsController do
context 'with an authenticated user' do
before do
sign_in(user)
- stub_experiment_for_user(onboarding_issues: true)
+ stub_experiment_for_subject(onboarding_issues: true)
end
context 'when not part of the onboarding issues experiment' do
before do
- stub_experiment_for_user(onboarding_issues: false)
+ stub_experiment_for_subject(onboarding_issues: false)
end
it { is_expected.to have_gitlab_http_status(:not_found) }
@@ -90,7 +90,7 @@ RSpec.describe Registrations::ExperienceLevelsController do
let(:issues_board) { build(:board, id: 123, project: project) }
before do
- stub_experiment_for_user(
+ stub_experiment_for_subject(
onboarding_issues: true,
default_to_issues_board: default_to_issues_board_xp?
)
diff --git a/spec/controllers/repositories/git_http_controller_spec.rb b/spec/controllers/repositories/git_http_controller_spec.rb
index 851c1b7e519..551abf9241d 100644
--- a/spec/controllers/repositories/git_http_controller_spec.rb
+++ b/spec/controllers/repositories/git_http_controller_spec.rb
@@ -3,219 +3,87 @@
require 'spec_helper'
RSpec.describe Repositories::GitHttpController do
- include GitHttpHelpers
-
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:personal_snippet) { create(:personal_snippet, :public, :repository) }
let_it_be(:project_snippet) { create(:project_snippet, :public, :repository, project: project) }
- let(:namespace_id) { project.namespace.to_param }
- let(:repository_id) { project.path + '.git' }
- let(:container_params) do
- {
- namespace_id: namespace_id,
- repository_id: repository_id
- }
- end
-
- let(:params) { container_params }
-
- describe 'HEAD #info_refs' do
- it 'returns 403' do
- head :info_refs, params: params
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- shared_examples 'info_refs behavior' do
- describe 'GET #info_refs' do
- let(:params) { container_params.merge(service: 'git-upload-pack') }
-
- it 'returns 401 for unauthenticated requests to public repositories when http protocol is disabled' do
- stub_application_setting(enabled_git_access_protocol: 'ssh')
- allow(controller).to receive(:basic_auth_provided?).and_call_original
-
- expect(controller).to receive(:http_download_allowed?).and_call_original
-
- get :info_refs, params: params
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
+ context 'when repository container is a project' do
+ it_behaves_like Repositories::GitHttpController do
+ let(:container) { project }
+ let(:user) { project.owner }
+ let(:access_checker_class) { Gitlab::GitAccess }
- context 'with authorized user' do
+ describe 'POST #git_upload_pack' do
before do
- request.headers.merge! auth_env(user.username, user.password, nil)
+ allow(controller).to receive(:verify_workhorse_api!).and_return(true)
end
- it 'returns 200' do
- get :info_refs, params: params
-
- expect(response).to have_gitlab_http_status(:ok)
+ def send_request
+ post :git_upload_pack, params: params
end
- it 'updates the user activity' do
- expect_next_instance_of(Users::ActivityService) do |activity_service|
- expect(activity_service).to receive(:execute)
+ context 'on a read-only instance' do
+ before do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
end
- get :info_refs, params: params
- end
-
- include_context 'parsed logs' do
- it 'adds user info to the logs' do
- get :info_refs, params: params
+ it 'does not update project statistics' do
+ expect(ProjectDailyStatisticsWorker).not_to receive(:perform_async)
- expect(log_data).to include('username' => user.username,
- 'user_id' => user.id,
- 'meta.user' => user.username)
+ send_request
end
end
- end
-
- context 'with exceptions' do
- before do
- allow(controller).to receive(:authenticate_user).and_return(true)
- allow(controller).to receive(:verify_workhorse_api!).and_return(true)
- end
-
- it 'returns 503 with GRPC Unavailable' do
- allow(controller).to receive(:access_check).and_raise(GRPC::Unavailable)
-
- get :info_refs, params: params
-
- expect(response).to have_gitlab_http_status(:service_unavailable)
- end
-
- it 'returns 503 with timeout error' do
- allow(controller).to receive(:access_check).and_raise(Gitlab::GitAccess::TimeoutError)
-
- get :info_refs, params: params
-
- expect(response).to have_gitlab_http_status(:service_unavailable)
- expect(response.body).to eq 'Gitlab::GitAccess::TimeoutError'
- end
- end
- end
- end
-
- shared_examples 'git_upload_pack behavior' do |expected|
- describe 'POST #git_upload_pack' do
- before do
- allow(controller).to receive(:authenticate_user).and_return(true)
- allow(controller).to receive(:verify_workhorse_api!).and_return(true)
- allow(controller).to receive(:access_check).and_return(nil)
- end
-
- def send_request
- post :git_upload_pack, params: params
- end
-
- context 'on a read-only instance' do
- before do
- allow(Gitlab::Database).to receive(:read_only?).and_return(true)
- end
- it 'does not update project statistics' do
- expect(ProjectDailyStatisticsWorker).not_to receive(:perform_async)
-
- send_request
- end
- end
-
- if expected
context 'when project_statistics_sync feature flag is disabled' do
before do
stub_feature_flags(project_statistics_sync: false)
end
- it 'updates project statistics async' do
+ it 'updates project statistics async for projects' do
expect(ProjectDailyStatisticsWorker).to receive(:perform_async)
send_request
end
end
- it 'updates project statistics sync' do
+ it 'updates project statistics sync for projects' do
expect { send_request }.to change {
- Projects::DailyStatisticsFinder.new(project).total_fetch_count
+ Projects::DailyStatisticsFinder.new(container).total_fetch_count
}.from(0).to(1)
end
- else
- context 'when project_statistics_sync feature flag is disabled' do
- before do
- stub_feature_flags(project_statistics_sync: false)
- end
-
- it 'does not update project statistics' do
- expect(ProjectDailyStatisticsWorker).not_to receive(:perform_async)
- send_request
+ it 'records a namespace onboarding progress action' do
+ expect_next_instance_of(OnboardingProgressService) do |service|
+ expect(service).to receive(:execute).with(action: :git_read)
end
- end
- it 'does not update project statistics' do
- expect { send_request }.not_to change {
- Projects::DailyStatisticsFinder.new(project).total_fetch_count
- }.from(0)
+ send_request
end
end
end
end
- shared_examples 'access checker class' do
- let(:params) { container_params.merge(service: 'git-upload-pack') }
-
- it 'calls the right access class checker with the right object' do
- allow(controller).to receive(:verify_workhorse_api!).and_return(true)
-
- access_double = double
- expect(expected_class).to receive(:new).with(anything, expected_object, 'http', anything).and_return(access_double)
- allow(access_double).to receive(:check).and_return(false)
-
- get :info_refs, params: params
- end
- end
-
- context 'when repository container is a project' do
- it_behaves_like 'info_refs behavior' do
+ context 'when repository container is a project wiki' do
+ it_behaves_like Repositories::GitHttpController do
+ let(:container) { create(:project_wiki, :empty_repo, project: project) }
let(:user) { project.owner }
- end
-
- it_behaves_like 'git_upload_pack behavior', true
- it_behaves_like 'access checker class' do
- let(:expected_class) { Gitlab::GitAccess }
- let(:expected_object) { project }
+ let(:access_checker_class) { Gitlab::GitAccessWiki }
end
end
context 'when repository container is a personal snippet' do
- let(:namespace_id) { 'snippets' }
- let(:repository_id) { personal_snippet.to_param + '.git' }
-
- it_behaves_like 'info_refs behavior' do
+ it_behaves_like Repositories::GitHttpController do
+ let(:container) { personal_snippet }
let(:user) { personal_snippet.author }
- end
-
- it_behaves_like 'git_upload_pack behavior', false
- it_behaves_like 'access checker class' do
- let(:expected_class) { Gitlab::GitAccessSnippet }
- let(:expected_object) { personal_snippet }
+ let(:access_checker_class) { Gitlab::GitAccessSnippet }
end
end
context 'when repository container is a project snippet' do
- let(:namespace_id) { project.full_path + '/snippets' }
- let(:repository_id) { project_snippet.to_param + '.git' }
-
- it_behaves_like 'info_refs behavior' do
+ it_behaves_like Repositories::GitHttpController do
+ let(:container) { project_snippet }
let(:user) { project_snippet.author }
- end
-
- it_behaves_like 'git_upload_pack behavior', false
- it_behaves_like 'access checker class' do
- let(:expected_class) { Gitlab::GitAccessSnippet }
- let(:expected_object) { project_snippet }
+ let(:access_checker_class) { Gitlab::GitAccessSnippet }
end
end
end
diff --git a/spec/controllers/repositories/lfs_storage_controller_spec.rb b/spec/controllers/repositories/lfs_storage_controller_spec.rb
index 4f9d049cf87..e361a7442bb 100644
--- a/spec/controllers/repositories/lfs_storage_controller_spec.rb
+++ b/spec/controllers/repositories/lfs_storage_controller_spec.rb
@@ -23,8 +23,7 @@ RSpec.describe Repositories::LfsStorageController do
let(:params) do
{
- namespace_id: project.namespace.path,
- repository_id: "#{project.path}.git",
+ repository_path: "#{project.full_path}.git",
oid: '6b9765d3888aaec789e8c309eb05b05c3a87895d6ad70d2264bd7270fff665ac',
size: '6725030'
}
diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb
index 1db99a09404..85f9ea66c5f 100644
--- a/spec/controllers/root_controller_spec.rb
+++ b/spec/controllers/root_controller_spec.rb
@@ -125,7 +125,7 @@ RSpec.describe RootController do
context 'when experiment is enabled' do
before do
- stub_experiment_for_user(customize_homepage: true)
+ stub_experiment_for_subject(customize_homepage: true)
end
it 'renders the default dashboard' do
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 993ab5d1c72..51cecb348c8 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -172,6 +172,13 @@ RSpec.describe SnippetsController do
expect(assigns(:snippet)).to eq(public_snippet)
expect(response).to have_gitlab_http_status(:ok)
end
+
+ it_behaves_like 'tracking unique hll events', :usage_data_i_snippets_show do
+ subject(:request) { get :show, params: { id: public_snippet.to_param } }
+
+ let(:target_id) { 'i_snippets_show' }
+ let(:expected_type) { instance_of(String) }
+ end
end
context 'when not signed in' do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 2e57a901319..916befe3f62 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe UsersController do
- let(:user) { create(:user) }
+ # This user should have the same e-mail address associated with the GPG key prepared for tests
+ let(:user) { create(:user, email: GpgHelpers::User1.emails[0]) }
let(:private_user) { create(:user, private_profile: true) }
let(:public_user) { create(:user) }
@@ -114,6 +115,335 @@ RSpec.describe UsersController do
end
end
+ describe 'GET #activity' do
+ context 'with rendered views' do
+ render_views
+
+ describe 'when logged in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the show template' do
+ get :show, params: { username: user.username }
+
+ expect(response).to be_successful
+ expect(response).to render_template('show')
+ end
+ end
+
+ describe 'when logged out' do
+ it 'renders the show template' do
+ get :activity, params: { username: user.username }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('show')
+ end
+ end
+ end
+
+ context 'when public visibility level is restricted' do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ context 'when logged out' do
+ it 'redirects to login page' do
+ get :activity, params: { username: user.username }
+ expect(response).to redirect_to new_user_session_path
+ end
+ end
+
+ context 'when logged in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders show' do
+ get :activity, params: { username: user.username }
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('show')
+ end
+ end
+ end
+
+ context 'when a user by that username does not exist' do
+ context 'when logged out' do
+ it 'redirects to login page' do
+ get :activity, params: { username: 'nonexistent' }
+ expect(response).to redirect_to new_user_session_path
+ end
+ end
+
+ context 'when logged in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders 404' do
+ get :activity, params: { username: 'nonexistent' }
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'json with events' do
+ let(:project) { create(:project) }
+
+ before do
+ project.add_developer(user)
+ Gitlab::DataBuilder::Push.build_sample(project, user)
+
+ sign_in(user)
+ end
+
+ it 'loads events' do
+ get :activity, params: { username: user }, format: :json
+
+ expect(assigns(:events)).not_to be_empty
+ end
+
+ it 'hides events if the user cannot read cross project' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
+
+ get :activity, params: { username: user }, format: :json
+
+ expect(assigns(:events)).to be_empty
+ end
+
+ it 'hides events if the user has a private profile' do
+ Gitlab::DataBuilder::Push.build_sample(project, private_user)
+
+ get :activity, params: { username: private_user.username }, format: :json
+
+ expect(assigns(:events)).to be_empty
+ end
+ end
+ end
+
+ describe "#ssh_keys" do
+ describe "non existent user" do
+ it "does not generally work" do
+ get :ssh_keys, params: { username: 'not-existent' }
+
+ expect(response).not_to be_successful
+ end
+ end
+
+ describe "user with no keys" do
+ it "does generally work" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response).to be_successful
+ end
+
+ it "renders all keys separated with a new line" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.body).to eq("")
+ end
+
+ it "responds with text/plain content type" do
+ get :ssh_keys, params: { username: user.username }
+ expect(response.content_type).to eq("text/plain")
+ end
+ end
+
+ describe "user with keys" do
+ let!(:key) { create(:key, user: user) }
+ let!(:another_key) { create(:another_key, user: user) }
+ let!(:deploy_key) { create(:deploy_key, user: user) }
+
+ describe "while signed in" do
+ before do
+ sign_in(user)
+ end
+
+ it "does generally work" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response).to be_successful
+ end
+
+ it "renders all non deploy keys separated with a new line" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.body).not_to eq('')
+ expect(response.body).to eq(user.all_ssh_keys.join("\n"))
+
+ expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
+ expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', ''))
+
+ expect(response.body).not_to include(deploy_key.key)
+ end
+
+ it "does not render the comment of the key" do
+ get :ssh_keys, params: { username: user.username }
+ expect(response.body).not_to match(/dummy@gitlab.com/)
+ end
+
+ it "responds with text/plain content type" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.content_type).to eq("text/plain")
+ end
+ end
+
+ describe 'when logged out' do
+ before do
+ sign_out(user)
+ end
+
+ it "still does generally work" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response).to be_successful
+ end
+
+ it "renders all non deploy keys separated with a new line" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.body).not_to eq('')
+ expect(response.body).to eq(user.all_ssh_keys.join("\n"))
+
+ expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
+ expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', ''))
+
+ expect(response.body).not_to include(deploy_key.key)
+ end
+
+ it "does not render the comment of the key" do
+ get :ssh_keys, params: { username: user.username }
+ expect(response.body).not_to match(/dummy@gitlab.com/)
+ end
+
+ it "responds with text/plain content type" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.content_type).to eq("text/plain")
+ end
+ end
+ end
+ end
+
+ describe "#gpg_keys" do
+ describe "non existent user" do
+ it "does not generally work" do
+ get :gpg_keys, params: { username: 'not-existent' }
+
+ expect(response).not_to be_successful
+ end
+ end
+
+ describe "user with no keys" do
+ it "does generally work" do
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response).to be_successful
+ end
+
+ it "renders all keys separated with a new line" do
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response.body).to eq("")
+ end
+
+ it "responds with text/plain content type" do
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response.content_type).to eq("text/plain")
+ end
+ end
+
+ describe "user with keys" do
+ let!(:gpg_key) { create(:gpg_key, user: user) }
+ let!(:another_gpg_key) { create(:another_gpg_key, user: user) }
+
+ describe "while signed in" do
+ before do
+ sign_in(user)
+ end
+
+ it "does generally work" do
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response).to be_successful
+ end
+
+ it "renders all verified keys separated with a new line" do
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response.body).not_to eq('')
+ expect(response.body).to eq(user.gpg_keys.select(&:verified?).map(&:key).join("\n"))
+
+ expect(response.body).to include(gpg_key.key)
+ expect(response.body).to include(another_gpg_key.key)
+ end
+
+ it "responds with text/plain content type" do
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response.content_type).to eq("text/plain")
+ end
+ end
+
+ describe 'when logged out' do
+ before do
+ sign_out(user)
+ end
+
+ it "still does generally work" do
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response).to be_successful
+ end
+
+ it "renders all verified keys separated with a new line" do
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response.body).not_to eq('')
+ expect(response.body).to eq(user.gpg_keys.map(&:key).join("\n"))
+
+ expect(response.body).to include(gpg_key.key)
+ expect(response.body).to include(another_gpg_key.key)
+ end
+
+ it "responds with text/plain content type" do
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response.content_type).to eq("text/plain")
+ end
+ end
+
+ describe 'when revoked' do
+ before do
+ sign_in(user)
+ another_gpg_key.revoke
+ end
+
+ it "doesn't render revoked keys" do
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response.body).not_to eq('')
+
+ expect(response.body).to include(gpg_key.key)
+ expect(response.body).not_to include(another_gpg_key.key)
+ end
+
+ it "doesn't render revoked keys for non-authorized users" do
+ sign_out(user)
+ get :gpg_keys, params: { username: user.username }
+
+ expect(response.body).not_to eq('')
+
+ expect(response.body).to include(gpg_key.key)
+ expect(response.body).not_to include(another_gpg_key.key)
+ end
+ end
+ end
+ end
+
describe 'GET #calendar' do
context 'for user' do
let(:project) { create(:project) }
diff --git a/spec/db/production/settings_spec.rb b/spec/db/production/settings_spec.rb
index f17720466c0..a7cd7201c89 100644
--- a/spec/db/production/settings_spec.rb
+++ b/spec/db/production/settings_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe 'seed production settings' do
stub_env('GITLAB_PROMETHEUS_METRICS_ENABLED', 'true')
end
- it 'prometheus_metrics_enabled is set to true ' do
+ it 'prometheus_metrics_enabled is set to true' do
load(settings_file)
expect(settings.prometheus_metrics_enabled).to eq(true)
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index c35f3831a58..a15b9624c9d 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe 'Database schema' do
audit_events_part_5fc467ac26: %w[author_id entity_id target_id],
award_emoji: %w[awardable_id user_id],
aws_roles: %w[role_external_id],
- boards: %w[milestone_id],
+ boards: %w[milestone_id iteration_id],
chat_names: %w[chat_id team_id user_id],
chat_teams: %w[team_id],
ci_builds: %w[erased_by_id runner_id trigger_request_id user_id],
@@ -86,7 +86,7 @@ RSpec.describe 'Database schema' do
users_star_projects: %w[user_id],
vulnerability_identifiers: %w[external_id],
vulnerability_scanners: %w[external_id],
- web_hooks: %w[service_id group_id]
+ web_hooks: %w[group_id]
}.with_indifferent_access.freeze
context 'for table' do
@@ -184,6 +184,7 @@ RSpec.describe 'Database schema' do
"ApplicationSetting" => %w[repository_storages_weighted],
"AlertManagement::Alert" => %w[payload],
"Ci::BuildMetadata" => %w[config_options config_variables],
+ "ExperimentUser" => %w[context],
"Geo::Event" => %w[payload],
"GeoNodeStatus" => %w[status],
"Operations::FeatureFlagScope" => %w[strategies],
diff --git a/spec/deprecation_toolkit_env.rb b/spec/deprecation_toolkit_env.rb
new file mode 100644
index 00000000000..bc90f67f0db
--- /dev/null
+++ b/spec/deprecation_toolkit_env.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+if ENV.key?('RECORD_DEPRECATIONS')
+ require 'deprecation_toolkit'
+ require 'deprecation_toolkit/rspec'
+ DeprecationToolkit::Configuration.test_runner = :rspec
+ DeprecationToolkit::Configuration.deprecation_path = 'deprecations'
+ DeprecationToolkit::Configuration.behavior = DeprecationToolkit::Behaviors::Record
+
+ # Enable ruby deprecations for keywords, it's suppressed by default in Ruby 2.7.2
+ Warning[:deprecated] = true
+
+ kwargs_warnings = [
+ # Taken from https://github.com/jeremyevans/ruby-warning/blob/1.1.0/lib/warning.rb#L18
+ %r{warning: (?:Using the last argument (?:for `.+' )?as keyword parameters is deprecated; maybe \*\* should be added to the call|Passing the keyword argument (?:for `.+' )?as the last hash parameter is deprecated|Splitting the last argument (?:for `.+' )?into positional and keyword parameters is deprecated|The called method (?:`.+' )?is defined here)\n\z}
+ ]
+ DeprecationToolkit::Configuration.warnings_treated_as_deprecation = kwargs_warnings
+end
diff --git a/spec/experiments/application_experiment/cache_spec.rb b/spec/experiments/application_experiment/cache_spec.rb
new file mode 100644
index 00000000000..a420d557155
--- /dev/null
+++ b/spec/experiments/application_experiment/cache_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ApplicationExperiment::Cache do
+ let(:key_name) { 'experiment_name' }
+ let(:field_name) { 'abc123' }
+ let(:key_field) { [key_name, field_name].join(':') }
+ let(:shared_state) { Gitlab::Redis::SharedState }
+
+ around do |example|
+ shared_state.with { |r| r.del(key_name) }
+ example.run
+ shared_state.with { |r| r.del(key_name) }
+ end
+
+ it "allows reading, writing and deleting", :aggregate_failures do
+ # we test them all together because they are largely interdependent
+
+ expect(subject.read(key_field)).to be_nil
+ expect(shared_state.with { |r| r.hget(key_name, field_name) }).to be_nil
+
+ subject.write(key_field, 'value')
+
+ expect(subject.read(key_field)).to eq('value')
+ expect(shared_state.with { |r| r.hget(key_name, field_name) }).to eq('value')
+
+ subject.delete(key_field)
+
+ expect(subject.read(key_field)).to be_nil
+ expect(shared_state.with { |r| r.hget(key_name, field_name) }).to be_nil
+ end
+
+ it "handles the fetch with a block behavior (which is read/write)" do
+ expect(subject.fetch(key_field) { 'value1' }).to eq('value1') # rubocop:disable Style/RedundantFetchBlock
+ expect(subject.fetch(key_field) { 'value2' }).to eq('value1') # rubocop:disable Style/RedundantFetchBlock
+ end
+
+ it "can clear a whole experiment cache key" do
+ subject.write(key_field, 'value')
+ subject.clear(key: key_field)
+
+ expect(shared_state.with { |r| r.get(key_name) }).to be_nil
+ end
+
+ it "doesn't allow clearing a key from the cache that's not a hash (definitely not an experiment)" do
+ shared_state.with { |r| r.set(key_name, 'value') }
+
+ expect { subject.clear(key: key_name) }.to raise_error(
+ ArgumentError,
+ 'invalid call to clear a non-hash cache key'
+ )
+ end
+
+ context "when the :caching_experiments feature is disabled" do
+ before do
+ stub_feature_flags(caching_experiments: false)
+ end
+
+ it "doesn't write to the cache" do
+ subject.write(key_field, 'value')
+
+ expect(subject.read(key_field)).to be_nil
+ end
+ end
+end
diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb
new file mode 100644
index 00000000000..ece52d37351
--- /dev/null
+++ b/spec/experiments/application_experiment_spec.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ApplicationExperiment do
+ subject { described_class.new(:stub) }
+
+ describe "publishing results" do
+ it "tracks the assignment" do
+ expect(subject).to receive(:track).with(:assignment)
+
+ subject.publish(nil)
+ end
+
+ it "pushes the experiment knowledge into the client using Gon.global" do
+ expect(Gon.global).to receive(:push).with(
+ {
+ experiment: {
+ 'stub' => { # string key because it can be namespaced
+ experiment: 'stub',
+ key: 'e8f65fd8d973f9985dc7ea3cf1614ae1',
+ variant: 'control'
+ }
+ }
+ },
+ true
+ )
+
+ subject.publish(nil)
+ end
+ end
+
+ describe "tracking events", :snowplow do
+ it "doesn't track if excluded" do
+ subject.exclude { true }
+
+ subject.track(:action)
+
+ expect_no_snowplow_event
+ end
+
+ it "tracks the event with the expected arguments and merged contexts" do
+ subject.track(:action, property: '_property_', context: [
+ SnowplowTracker::SelfDescribingJson.new('iglu:com.gitlab/fake/jsonschema/0-0-0', { data: '_data_' })
+ ])
+
+ expect_snowplow_event(
+ category: 'stub',
+ action: 'action',
+ property: '_property_',
+ context: [
+ {
+ schema: 'iglu:com.gitlab/fake/jsonschema/0-0-0',
+ data: { data: '_data_' }
+ },
+ {
+ schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/0-3-0',
+ data: { experiment: 'stub', key: 'e8f65fd8d973f9985dc7ea3cf1614ae1', variant: 'control' }
+ }
+ ]
+ )
+ end
+ end
+
+ describe "variant resolution" do
+ it "returns nil when not rolled out" do
+ stub_feature_flags(stub: false)
+
+ expect(subject.variant.name).to eq('control')
+ end
+
+ context "when rolled out to 100%" do
+ it "returns the first variant name" do
+ subject.try(:variant1) {}
+ subject.try(:variant2) {}
+
+ expect(subject.variant.name).to eq('variant1')
+ end
+ end
+ end
+
+ context "when caching" do
+ let(:cache) { ApplicationExperiment::Cache.new }
+
+ before do
+ cache.clear(key: subject.name)
+
+ subject.use { } # setup the control
+ subject.try { } # setup the candidate
+
+ allow(Gitlab::Experiment::Configuration).to receive(:cache).and_return(cache)
+ end
+
+ it "caches the variant determined by the variant resolver" do
+ expect(subject.variant.name).to eq('candidate') # we should be in the experiment
+
+ subject.run
+
+ expect(cache.read(subject.cache_key)).to eq('candidate')
+ end
+
+ it "doesn't cache a variant if we don't explicitly provide one" do
+ # by not caching "empty" variants, we effectively create a mostly
+ # optimal combination of caching and rollout flexibility. If we cached
+ # every control variant assigned, we'd inflate the cache size and
+ # wouldn't be able to roll out to subjects that we'd already assigned to
+ # the control.
+ stub_feature_flags(stub: false) # simulate being not rolled out
+
+ expect(subject.variant.name).to eq('control') # if we ask, it should be control
+
+ subject.run
+
+ expect(cache.read(subject.cache_key)).to be_nil
+ end
+
+ it "caches a control variant if we assign it specifically" do
+ # by specifically assigning the control variant here, we're guaranteeing
+ # that this context will always get the control variant unless we delete
+ # the field from the cache (or clear the entire experiment cache) -- or
+ # write code that would specify a different variant.
+ subject.run(:control)
+
+ expect(cache.read(subject.cache_key)).to eq('control')
+ end
+ end
+end
diff --git a/spec/factories/alert_management/http_integrations.rb b/spec/factories/alert_management/http_integrations.rb
index 2b5864c8587..405ec09251f 100644
--- a/spec/factories/alert_management/http_integrations.rb
+++ b/spec/factories/alert_management/http_integrations.rb
@@ -11,6 +11,10 @@ FactoryBot.define do
active { false }
end
+ trait :active do
+ active { true }
+ end
+
trait :legacy do
endpoint_identifier { 'legacy' }
end
diff --git a/spec/factories/analytics/devops_adoption/segment_selections.rb b/spec/factories/analytics/devops_adoption/segment_selections.rb
deleted file mode 100644
index 8f10369ba35..00000000000
--- a/spec/factories/analytics/devops_adoption/segment_selections.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-FactoryBot.define do
- factory :devops_adoption_segment_selection, class: 'Analytics::DevopsAdoption::SegmentSelection' do
- association :segment, factory: :devops_adoption_segment
- project
-
- trait :project do
- group { nil }
- project
- end
-
- trait :group do
- project { nil }
- group
- end
- end
-end
diff --git a/spec/factories/analytics/devops_adoption/segments.rb b/spec/factories/analytics/devops_adoption/segments.rb
deleted file mode 100644
index 367ee01fa18..00000000000
--- a/spec/factories/analytics/devops_adoption/segments.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-FactoryBot.define do
- factory :devops_adoption_segment, class: 'Analytics::DevopsAdoption::Segment' do
- sequence(:name) { |n| "Segment #{n}" }
- end
-end
diff --git a/spec/factories/bulk_import/failures.rb b/spec/factories/bulk_import/failures.rb
new file mode 100644
index 00000000000..1ebdfdd6c42
--- /dev/null
+++ b/spec/factories/bulk_import/failures.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'securerandom'
+
+FactoryBot.define do
+ factory :bulk_import_failure, class: 'BulkImports::Failure' do
+ association :entity, factory: :bulk_import_entity
+
+ pipeline_class { 'BulkImports::TestPipeline' }
+ exception_class { 'StandardError' }
+ exception_message { 'Standard Error Message' }
+ correlation_id_value { SecureRandom.uuid }
+ end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 11719e40cf2..c3d6e9d7569 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -356,6 +356,12 @@ FactoryBot.define do
end
end
+ trait :codequality_reports do
+ after(:build) do |build|
+ build.job_artifacts << create(:ci_job_artifact, :codequality, job: build)
+ end
+ end
+
trait :terraform_reports do
after(:build) do |build|
build.job_artifacts << create(:ci_job_artifact, :terraform, job: build)
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 223184891b7..ad98e9d1f24 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -229,6 +229,16 @@ FactoryBot.define do
end
end
+ trait :coverage_with_paths_not_relative_to_project_root do
+ file_type { :cobertura }
+ file_format { :gzip }
+
+ after(:build) do |artifact, evaluator|
+ artifact.file = fixture_file_upload(
+ Rails.root.join('spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz'), 'application/x-gzip')
+ end
+ end
+
trait :coverage_with_corrupted_data do
file_type { :cobertura }
file_format { :gzip }
@@ -245,7 +255,17 @@ FactoryBot.define do
after(:build) do |artifact, evaluator|
artifact.file = fixture_file_upload(
- Rails.root.join('spec/fixtures/codequality/codequality.json'), 'application/json')
+ Rails.root.join('spec/fixtures/codequality/codeclimate.json'), 'application/json')
+ end
+ end
+
+ trait :codequality_without_errors do
+ file_type { :codequality }
+ file_format { :raw }
+
+ after(:build) do |artifact, evaluator|
+ artifact.file = fixture_file_upload(
+ Rails.root.join('spec/fixtures/codequality/codeclimate_without_errors.json'), 'application/json')
end
end
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 14bd0ab1bc6..86a8b008e48 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -16,6 +16,7 @@ FactoryBot.define do
transient { head_pipeline_of { nil } }
transient { child_of { nil } }
+ transient { upstream_of { nil } }
after(:build) do |pipeline, evaluator|
if evaluator.child_of
@@ -30,9 +31,12 @@ FactoryBot.define do
if evaluator.child_of
bridge = create(:ci_bridge, pipeline: evaluator.child_of)
- create(:ci_sources_pipeline,
- source_job: bridge,
- pipeline: pipeline)
+ create(:ci_sources_pipeline, source_job: bridge, pipeline: pipeline)
+ end
+
+ if evaluator.upstream_of
+ bridge = create(:ci_bridge, pipeline: pipeline)
+ create(:ci_sources_pipeline, source_job: bridge, pipeline: evaluator.upstream_of)
end
end
@@ -122,10 +126,10 @@ FactoryBot.define do
end
trait :with_test_reports_with_three_failures do
- status { :success }
+ status { :failed }
after(:build) do |pipeline, _evaluator|
- pipeline.builds << build(:ci_build, :test_reports_with_three_failures, pipeline: pipeline, project: pipeline.project)
+ pipeline.builds << build(:ci_build, :failed, :test_reports_with_three_failures, pipeline: pipeline, project: pipeline.project)
end
end
@@ -145,6 +149,14 @@ FactoryBot.define do
end
end
+ trait :with_codequality_reports do
+ status { :success }
+
+ after(:build) do |pipeline, evaluator|
+ pipeline.builds << build(:ci_build, :codequality_reports, pipeline: pipeline, project: pipeline.project)
+ end
+ end
+
trait :with_coverage_report_artifact do
after(:build) do |pipeline, evaluator|
pipeline.pipeline_artifacts << build(:ci_pipeline_artifact, pipeline: pipeline, project: pipeline.project)
diff --git a/spec/factories/dependency_proxy.rb b/spec/factories/dependency_proxy.rb
index 5d763392a99..de95df19876 100644
--- a/spec/factories/dependency_proxy.rb
+++ b/spec/factories/dependency_proxy.rb
@@ -6,4 +6,11 @@ FactoryBot.define do
file { fixture_file_upload('spec/fixtures/dependency_proxy/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz') }
file_name { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz' }
end
+
+ factory :dependency_proxy_manifest, class: 'DependencyProxy::Manifest' do
+ group
+ file { fixture_file_upload('spec/fixtures/dependency_proxy/manifest') }
+ digest { 'sha256:5ab5a6872b264fe4fd35d63991b9b7d8425f4bc79e7cf4d563c10956581170c9' }
+ file_name { 'alpine:latest.json' }
+ end
end
diff --git a/spec/factories/experiment_subjects.rb b/spec/factories/experiment_subjects.rb
new file mode 100644
index 00000000000..c35bc370bad
--- /dev/null
+++ b/spec/factories/experiment_subjects.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :experiment_subject do
+ experiment
+ user
+ variant { :control }
+ end
+end
diff --git a/spec/factories/experiment_users.rb b/spec/factories/experiment_users.rb
new file mode 100644
index 00000000000..66c39d684eb
--- /dev/null
+++ b/spec/factories/experiment_users.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :experiment_user do
+ experiment
+ user
+ group_type { :control }
+ converted_at { nil }
+ end
+end
diff --git a/spec/factories/exported_protected_branches.rb b/spec/factories/exported_protected_branches.rb
new file mode 100644
index 00000000000..7ad49b12e5b
--- /dev/null
+++ b/spec/factories/exported_protected_branches.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :exported_protected_branch, class: 'ExportedProtectedBranch', parent: :protected_branch
+end
diff --git a/spec/factories/gitlab/database/postgres_index.rb b/spec/factories/gitlab/database/postgres_index.rb
new file mode 100644
index 00000000000..54bbb738512
--- /dev/null
+++ b/spec/factories/gitlab/database/postgres_index.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :postgres_index, class: 'Gitlab::Database::PostgresIndex' do
+ identifier { "public.some_index_#{indexrelid}" }
+ sequence(:indexrelid) { |n| n }
+ schema { 'public' }
+ name { "some_index_#{indexrelid}" }
+ tablename { 'foo' }
+ unique { false }
+ valid_index { true }
+ partitioned { false }
+ exclusion { false }
+ expression { false }
+ partial { false }
+ definition { "CREATE INDEX #{identifier} ON #{tablename} (bar)"}
+ ondisk_size_bytes { 100.megabytes }
+ end
+end
diff --git a/spec/factories/gitlab/database/postgres_index_bloat_estimate.rb b/spec/factories/gitlab/database/postgres_index_bloat_estimate.rb
new file mode 100644
index 00000000000..878650714b8
--- /dev/null
+++ b/spec/factories/gitlab/database/postgres_index_bloat_estimate.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :postgres_index_bloat_estimate, class: 'Gitlab::Database::PostgresIndexBloatEstimate' do
+ association :index, factory: :postgres_index
+
+ identifier { index.identifier }
+ bloat_size_bytes { 10.megabytes }
+ end
+end
diff --git a/spec/factories/gitlab/database/reindexing/reindex_action.rb b/spec/factories/gitlab/database/reindexing/reindex_action.rb
new file mode 100644
index 00000000000..4b558286463
--- /dev/null
+++ b/spec/factories/gitlab/database/reindexing/reindex_action.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :reindex_action, class: 'Gitlab::Database::Reindexing::ReindexAction' do
+ association :index, factory: :postgres_index
+
+ action_start { Time.now - 10.minutes }
+ action_end { Time.now - 5.minutes }
+ ondisk_size_bytes_start { 2.megabytes }
+ ondisk_size_bytes_end { 1.megabytes }
+ state { Gitlab::Database::Reindexing::ReindexAction.states[:finished] }
+ index_identifier { index.identifier }
+ end
+end
diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb
index 9f321643174..5967d9ba9d3 100644
--- a/spec/factories/gpg_keys.rb
+++ b/spec/factories/gpg_keys.rb
@@ -10,5 +10,10 @@ FactoryBot.define do
factory :gpg_key_with_subkeys do
key { GpgHelpers::User1.public_key_with_extra_signing_key }
end
+
+ factory :another_gpg_key do
+ key { GpgHelpers::User1.public_key2 }
+ user
+ end
end
end
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 90e43b9e22c..5c62de4d08d 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -52,9 +52,5 @@ FactoryBot.define do
factory :incident do
issue_type { :incident }
end
-
- factory :quality_test_case do
- issue_type { :test_case }
- end
end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index e5381071228..e69743122cc 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -24,6 +24,14 @@ FactoryBot.define do
trait :with_diffs do
end
+ trait :jira_title do
+ title { generate(:jira_title) }
+ end
+
+ trait :jira_branch do
+ source_branch { generate(:jira_branch) }
+ end
+
trait :with_image_diffs do
source_branch { "add_images_and_changes" }
target_branch { "master" }
@@ -52,7 +60,7 @@ FactoryBot.define do
after(:build) do |merge_request, evaluator|
metrics = merge_request.build_metrics
- metrics.merged_at = 1.week.ago
+ metrics.merged_at = 1.week.from_now
metrics.merged_by = evaluator.merged_by
metrics.pipeline = create(:ci_empty_pipeline)
end
@@ -159,6 +167,18 @@ FactoryBot.define do
end
end
+ trait :with_codequality_reports do
+ after(:build) do |merge_request|
+ merge_request.head_pipeline = build(
+ :ci_pipeline,
+ :success,
+ :with_codequality_reports,
+ project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+ end
+ end
+
trait :unique_branches do
source_branch { generate(:branch) }
target_branch { generate(:branch) }
@@ -237,7 +257,7 @@ FactoryBot.define do
target_branch { 'pages-deploy-target' }
transient do
- deployment { create(:deployment, :review_app) }
+ deployment { association(:deployment, :review_app) }
end
after(:build) do |merge_request, evaluator|
@@ -256,7 +276,7 @@ FactoryBot.define do
source_project = merge_request.source_project
# Fake `fetch_ref!` if we don't have repository
- # We have too many existing tests replying on this behaviour
+ # We have too many existing tests relying on this behaviour
unless [target_project, source_project].all?(&:repository_exists?)
allow(merge_request).to receive(:fetch_ref!)
end
diff --git a/spec/factories/namespace_onboarding_actions.rb b/spec/factories/namespace_onboarding_actions.rb
new file mode 100644
index 00000000000..aca62013b57
--- /dev/null
+++ b/spec/factories/namespace_onboarding_actions.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :namespace_onboarding_action do
+ namespace
+ action { :subscription_created }
+ end
+end
diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb
index 73870a28b89..5543be4aa82 100644
--- a/spec/factories/packages.rb
+++ b/spec/factories/packages.rb
@@ -22,7 +22,14 @@ FactoryBot.define do
end
factory :debian_package do
+ sequence(:name) { |n| "package-#{n}" }
+ sequence(:version) { |n| "1.0-#{n}" }
package_type { :debian }
+
+ factory :debian_incoming do
+ name { 'incoming' }
+ version { nil }
+ end
end
factory :npm_package do
diff --git a/spec/factories/packages/package_file.rb b/spec/factories/packages/package_file.rb
index 643ab8e4f95..bee1b2076df 100644
--- a/spec/factories/packages/package_file.rb
+++ b/spec/factories/packages/package_file.rb
@@ -152,14 +152,6 @@ FactoryBot.define do
file_store { Packages::PackageFileUploader::Store::REMOTE }
end
- trait(:checksummed) do
- verification_checksum { 'abc' }
- end
-
- trait(:checksum_failure) do
- verification_failure { 'Could not calculate the checksum' }
- end
-
factory :package_file_with_file, traits: [:jar]
end
end
diff --git a/spec/factories/personal_access_tokens.rb b/spec/factories/personal_access_tokens.rb
index d8ff2e08657..9625fdc195d 100644
--- a/spec/factories/personal_access_tokens.rb
+++ b/spec/factories/personal_access_tokens.rb
@@ -26,5 +26,9 @@ FactoryBot.define do
trait :invalid do
token_digest { nil }
end
+
+ trait :no_prefix do
+ after(:build) { |personal_access_token| personal_access_token.set_token(Devise.friendly_token) }
+ end
end
end
diff --git a/spec/factories/project_repository_storage_moves.rb b/spec/factories/project_repository_storage_moves.rb
index c0068de5f58..5df2b7c32d6 100644
--- a/spec/factories/project_repository_storage_moves.rb
+++ b/spec/factories/project_repository_storage_moves.rb
@@ -2,7 +2,7 @@
FactoryBot.define do
factory :project_repository_storage_move, class: 'ProjectRepositoryStorageMove' do
- project
+ container { association(:project) }
source_storage_name { 'default' }
diff --git a/spec/factories/project_settings.rb b/spec/factories/project_settings.rb
new file mode 100644
index 00000000000..c0c5039ab51
--- /dev/null
+++ b/spec/factories/project_settings.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_setting do
+ project
+ end
+end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 639fff06cec..29a25e71095 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -32,10 +32,13 @@ FactoryBot.define do
visibility_level == Gitlab::VisibilityLevel::PUBLIC ? ProjectFeature::ENABLED : ProjectFeature::PRIVATE
end
metrics_dashboard_access_level { ProjectFeature::PRIVATE }
+ operations_access_level { ProjectFeature::ENABLED }
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
group_runners_enabled { nil }
+ merge_pipelines_enabled { nil }
+ merge_trains_enabled { nil }
import_status { nil }
import_jid { nil }
import_correlation_id { nil }
@@ -57,7 +60,8 @@ FactoryBot.define do
merge_requests_access_level: merge_requests_access_level,
repository_access_level: evaluator.repository_access_level,
pages_access_level: evaluator.pages_access_level,
- metrics_dashboard_access_level: evaluator.metrics_dashboard_access_level
+ metrics_dashboard_access_level: evaluator.metrics_dashboard_access_level,
+ operations_access_level: evaluator.operations_access_level
}
project.build_project_feature(hash)
@@ -75,7 +79,9 @@ FactoryBot.define do
project.group&.refresh_members_authorized_projects
# assign the delegated `#ci_cd_settings` attributes after create
- project.reload.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil?
+ project.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil?
+ project.merge_pipelines_enabled = evaluator.merge_pipelines_enabled unless evaluator.merge_pipelines_enabled.nil?
+ project.merge_trains_enabled = evaluator.merge_trains_enabled unless evaluator.merge_trains_enabled.nil?
if evaluator.import_status
import_state = project.import_state || project.build_import_state
@@ -322,6 +328,9 @@ FactoryBot.define do
trait(:metrics_dashboard_enabled) { metrics_dashboard_access_level { ProjectFeature::ENABLED } }
trait(:metrics_dashboard_disabled) { metrics_dashboard_access_level { ProjectFeature::DISABLED } }
trait(:metrics_dashboard_private) { metrics_dashboard_access_level { ProjectFeature::PRIVATE } }
+ trait(:operations_enabled) { operations_access_level { ProjectFeature::ENABLED } }
+ trait(:operations_disabled) { operations_access_level { ProjectFeature::DISABLED } }
+ trait(:operations_private) { operations_access_level { ProjectFeature::PRIVATE } }
trait :auto_devops do
association :auto_devops, factory: :project_auto_devops
@@ -389,10 +398,6 @@ FactoryBot.define do
jira_service
end
- factory :mock_deployment_project, parent: :project do
- mock_deployment_service
- end
-
factory :prometheus_project, parent: :project do
after :create do |project|
project.create_prometheus_service(
diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb
index 11116af7dbb..f10a3235202 100644
--- a/spec/factories/sent_notifications.rb
+++ b/spec/factories/sent_notifications.rb
@@ -4,7 +4,7 @@ FactoryBot.define do
factory :sent_notification do
project
recipient { project.creator }
- noteable { create(:issue, project: project) }
+ noteable { association(:issue, project: project) }
reply_key { SentNotification.reply_key }
end
end
diff --git a/spec/factories/sequences.rb b/spec/factories/sequences.rb
index ca0804965df..b338fd99625 100644
--- a/spec/factories/sequences.rb
+++ b/spec/factories/sequences.rb
@@ -13,4 +13,8 @@ FactoryBot.define do
sequence(:past_time) { |n| 4.hours.ago + (2 * n).seconds }
sequence(:iid)
sequence(:sha) { |n| Digest::SHA1.hexdigest("commit-like-#{n}") }
+ sequence(:oid) { |n| Digest::SHA2.hexdigest("oid-like-#{n}") }
+ sequence(:variable) { |n| "var#{n}" }
+ sequence(:jira_title) { |n| "[PROJ-#{n}]: fix bug" }
+ sequence(:jira_branch) { |n| "feature/PROJ-#{n}" }
end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 1935ace8e96..44b157014a5 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -27,12 +27,6 @@ FactoryBot.define do
end
end
- factory :mock_deployment_service do
- project
- type { 'MockDeploymentService' }
- active { true }
- end
-
factory :prometheus_service do
project
active { true }
diff --git a/spec/factories/snippet_repository_storage_moves.rb b/spec/factories/snippet_repository_storage_moves.rb
new file mode 100644
index 00000000000..ed65dc5374f
--- /dev/null
+++ b/spec/factories/snippet_repository_storage_moves.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :snippet_repository_storage_move, class: 'SnippetRepositoryStorageMove' do
+ container { association(:snippet) }
+
+ source_storage_name { 'default' }
+
+ trait :scheduled do
+ state { SnippetRepositoryStorageMove.state_machines[:state].states[:scheduled].value }
+ end
+
+ trait :started do
+ state { SnippetRepositoryStorageMove.state_machines[:state].states[:started].value }
+ end
+
+ trait :replicated do
+ state { SnippetRepositoryStorageMove.state_machines[:state].states[:replicated].value }
+ end
+
+ trait :finished do
+ state { SnippetRepositoryStorageMove.state_machines[:state].states[:finished].value }
+ end
+
+ trait :failed do
+ state { SnippetRepositoryStorageMove.state_machines[:state].states[:failed].value }
+ end
+ end
+end
diff --git a/spec/factories/terraform/state.rb b/spec/factories/terraform/state.rb
index c54a8aedbc6..fb63c845073 100644
--- a/spec/factories/terraform/state.rb
+++ b/spec/factories/terraform/state.rb
@@ -6,11 +6,6 @@ FactoryBot.define do
sequence(:name) { |n| "state-#{n}" }
- trait :with_file do
- versioning_enabled { false }
- file { fixture_file_upload('spec/fixtures/terraform/terraform.tfstate', 'application/json') }
- end
-
trait :locked do
sequence(:lock_xid) { |n| "lock-#{n}" }
locked_at { Time.current }
@@ -22,8 +17,5 @@ FactoryBot.define do
create(:terraform_state_version, terraform_state: state)
end
end
-
- # Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/235108
- factory :legacy_terraform_state, parent: :terraform_state, traits: [:with_file]
end
end
diff --git a/spec/factories/usage_data.rb b/spec/factories/usage_data.rb
index 87f806a3d74..f933461a07a 100644
--- a/spec/factories/usage_data.rb
+++ b/spec/factories/usage_data.rb
@@ -19,15 +19,16 @@ FactoryBot.define do
create(:jira_import_state, :finished, project: projects[1], label: jira_label, imported_issues_count: 3)
create(:jira_import_state, :scheduled, project: projects[1], label: jira_label)
create(:prometheus_service, project: projects[1])
+ create(:service, project: projects[1], type: 'JenkinsService', active: true)
create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
create(:service, project: projects[1], type: 'SlackService', active: true)
create(:service, project: projects[2], type: 'SlackService', active: true)
create(:service, project: projects[2], type: 'MattermostService', active: false)
create(:service, group: group, project: nil, type: 'MattermostService', active: true)
create(:service, :template, type: 'MattermostService', active: true)
- matermost_instance = create(:service, :instance, type: 'MattermostService', active: true)
- create(:service, project: projects[1], type: 'MattermostService', active: true, inherit_from_id: matermost_instance.id)
- create(:service, group: group, project: nil, type: 'SlackService', active: true, inherit_from_id: matermost_instance.id)
+ mattermost_instance = create(:service, :instance, type: 'MattermostService', active: true)
+ create(:service, project: projects[1], type: 'MattermostService', active: true, inherit_from_id: mattermost_instance.id)
+ create(:service, group: group, project: nil, type: 'SlackService', active: true, inherit_from_id: mattermost_instance.id)
create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true)
create(:project_error_tracking_setting, project: projects[0])
create(:project_error_tracking_setting, project: projects[1], enabled: false)
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 1b430009ab5..9b5e4a981a0 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -35,6 +35,10 @@ FactoryBot.define do
user_type { :alert_bot }
end
+ trait :deactivated do
+ after(:build) { |user, _| user.deactivate! }
+ end
+
trait :project_bot do
user_type { :project_bot }
end
@@ -43,6 +47,10 @@ FactoryBot.define do
user_type { :migration_bot }
end
+ trait :security_bot do
+ user_type { :security_bot }
+ end
+
trait :external do
external { true }
end
diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb
index 7241af6e8c0..1afd5bdb2ca 100644
--- a/spec/factories_spec.rb
+++ b/spec/factories_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'factories' do
+ include DatabaseHelpers
+
shared_examples 'factory' do |factory|
describe "#{factory.name} factory" do
it 'does not raise error when built' do
@@ -32,6 +34,14 @@ RSpec.describe 'factories' do
fork_network_member
].to_set.freeze
+ # Some factories and their corresponding models are based on
+ # database views. In order to use those, we have to swap the
+ # view out with a table of the same structure.
+ factories_based_on_view = %i[
+ postgres_index
+ postgres_index_bloat_estimate
+ ].to_set.freeze
+
without_fd, with_fd = FactoryBot.factories
.partition { |factory| skip_factory_defaults.include?(factory.name) }
@@ -40,6 +50,13 @@ RSpec.describe 'factories' do
let_it_be(:project) { create_default(:project, :repository) }
let_it_be(:user) { create_default(:user) }
+ before do
+ factories_based_on_view.each do |factory|
+ view = build(factory).class.table_name
+ swapout_view_for_table(view)
+ end
+ end
+
with_fd.each do |factory|
it_behaves_like 'factory', factory
end
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index 5959fcd6306..5fdd0816006 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe 'Abuse reports' do
sign_in(create(:user))
end
- it 'Report abuse' do
+ it 'report abuse' do
visit user_path(another_user)
click_link 'Report abuse'
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 845e186dd5b..192182adddc 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -7,7 +7,9 @@ RSpec.describe "Admin::AbuseReports", :js do
context 'as an admin' do
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
describe 'if a user has been reported for abuse' do
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index 48aaec6e6df..cd136af8d69 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -4,9 +4,11 @@ require 'spec_helper'
RSpec.describe 'Admin Appearance' do
let!(:appearance) { create(:appearance) }
+ let(:admin) { create(:admin) }
- it 'Create new appearance' do
- sign_in(create(:admin))
+ it 'create new appearance' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit admin_appearances_path
fill_in 'appearance_title', with: 'MyCompany'
@@ -25,8 +27,9 @@ RSpec.describe 'Admin Appearance' do
expect(page).to have_content 'Last edit'
end
- it 'Preview sign-in page appearance' do
- sign_in(create(:admin))
+ it 'preview sign-in page appearance' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit admin_appearances_path
click_link "Sign-in page"
@@ -34,8 +37,9 @@ RSpec.describe 'Admin Appearance' do
expect_custom_sign_in_appearance(appearance)
end
- it 'Preview new project page appearance' do
- sign_in(create(:admin))
+ it 'preview new project page appearance' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit admin_appearances_path
click_link "New project page"
@@ -45,7 +49,8 @@ RSpec.describe 'Admin Appearance' do
context 'Custom system header and footer' do
before do
- sign_in(create(:admin))
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
context 'when system header and footer messages are empty' do
@@ -75,14 +80,15 @@ RSpec.describe 'Admin Appearance' do
end
end
- it 'Custom sign-in page' do
+ it 'custom sign-in page' do
visit new_user_session_path
expect_custom_sign_in_appearance(appearance)
end
- it 'Custom new project page' do
- sign_in create(:user)
+ it 'custom new project page' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit new_project_path
expect_custom_new_project_appearance(appearance)
@@ -91,6 +97,7 @@ RSpec.describe 'Admin Appearance' do
context 'Profile page with custom profile image guidelines' do
before do
sign_in(create(:admin))
+ gitlab_enable_admin_mode_sign_in(admin)
visit admin_appearances_path
fill_in 'appearance_profile_image_guidelines', with: 'Custom profile image guidelines, please :smile:!'
click_button 'Update appearance settings'
@@ -104,8 +111,9 @@ RSpec.describe 'Admin Appearance' do
end
end
- it 'Appearance logo' do
- sign_in(create(:admin))
+ it 'appearance logo' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit admin_appearances_path
attach_file(:appearance_logo, logo_fixture)
@@ -116,8 +124,9 @@ RSpec.describe 'Admin Appearance' do
expect(page).not_to have_css(logo_selector)
end
- it 'Header logos' do
- sign_in(create(:admin))
+ it 'header logos' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit admin_appearances_path
attach_file(:appearance_header_logo, logo_fixture)
@@ -129,7 +138,8 @@ RSpec.describe 'Admin Appearance' do
end
it 'Favicon' do
- sign_in(create(:admin))
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit admin_appearances_path
attach_file(:appearance_favicon, logo_fixture)
diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb
index 091ed0a3396..476dd4469bc 100644
--- a/spec/features/admin/admin_broadcast_messages_spec.rb
+++ b/spec/features/admin/admin_broadcast_messages_spec.rb
@@ -4,12 +4,14 @@ require 'spec_helper'
RSpec.describe 'Admin Broadcast Messages' do
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
create(:broadcast_message, :expired, message: 'Migration to new server')
visit admin_broadcast_messages_path
end
- it 'See broadcast messages list' do
+ it 'see broadcast messages list' do
expect(page).to have_content 'Migration to new server'
end
@@ -42,7 +44,7 @@ RSpec.describe 'Admin Broadcast Messages' do
expect(page).to have_selector 'strong', text: '4:00 CST to 5:00 CST'
end
- it 'Edit an existing broadcast message' do
+ it 'edit an existing broadcast message' do
click_link 'Edit'
fill_in 'broadcast_message_message', with: 'Application update RIGHT NOW'
click_button 'Update broadcast message'
@@ -51,7 +53,7 @@ RSpec.describe 'Admin Broadcast Messages' do
expect(page).to have_content 'Application update RIGHT NOW'
end
- it 'Remove an existing broadcast message' do
+ it 'remove an existing broadcast message' do
click_link 'Remove'
expect(current_path).to eq admin_broadcast_messages_path
diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb
index 65847876c11..471a7e8f0ab 100644
--- a/spec/features/admin/admin_browse_spam_logs_spec.rb
+++ b/spec/features/admin/admin_browse_spam_logs_spec.rb
@@ -6,10 +6,12 @@ RSpec.describe 'Admin browse spam logs' do
let!(:spam_log) { create(:spam_log, description: 'abcde ' * 20) }
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
- it 'Browse spam logs' do
+ it 'browse spam logs' do
visit admin_spam_logs_path
expect(page).to have_content('Spam Logs')
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index 166fde0f37a..42827dd5b49 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
RSpec.describe 'Admin Builds' do
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
describe 'GET /admin/builds' do
diff --git a/spec/features/admin/admin_cohorts_spec.rb b/spec/features/admin/admin_cohorts_spec.rb
index f91446ed222..982a9333275 100644
--- a/spec/features/admin/admin_cohorts_spec.rb
+++ b/spec/features/admin/admin_cohorts_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
RSpec.describe 'Cohorts page' do
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
context 'with usage ping enabled' do
diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb
index 2039a6ff1ee..c326d0fd741 100644
--- a/spec/features/admin/admin_deploy_keys_spec.rb
+++ b/spec/features/admin/admin_deploy_keys_spec.rb
@@ -7,7 +7,9 @@ RSpec.describe 'admin deploy keys' do
let!(:another_deploy_key) { create(:another_deploy_key, public: true) }
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
it 'show all public deploy keys' do
diff --git a/spec/features/admin/admin_dev_ops_report_spec.rb b/spec/features/admin/admin_dev_ops_report_spec.rb
index 3b2c9d75870..a05fa0640d8 100644
--- a/spec/features/admin/admin_dev_ops_report_spec.rb
+++ b/spec/features/admin/admin_dev_ops_report_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
RSpec.describe 'DevOps Report page', :js do
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
context 'with devops_adoption feature flag disabled' do
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index d7feb21a8b3..f7f0592a315 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe 'Admin disables Git access protocol', :js do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
context 'with HTTP disabled' do
diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb
index 216c8ae36c7..1f34c4ed17c 100644
--- a/spec/features/admin/admin_disables_two_factor_spec.rb
+++ b/spec/features/admin/admin_disables_two_factor_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
RSpec.describe 'Admin disables 2FA for a user' do
it 'successfully', :js do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
user = create(:user, :two_factor)
edit_user(user)
@@ -19,7 +21,9 @@ RSpec.describe 'Admin disables 2FA for a user' do
end
it 'for a user without 2FA enabled' do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
user = create(:user)
edit_user(user)
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 96709cf8a12..0e350a5e12e 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -7,12 +7,14 @@ RSpec.describe 'Admin Groups' do
include Spec::Support::Helpers::Features::MembersHelpers
let(:internal) { Gitlab::VisibilityLevel::INTERNAL }
- let(:user) { create :user }
- let!(:group) { create :group }
- let!(:current_user) { create(:admin) }
+
+ let_it_be(:user) { create :user }
+ let_it_be(:group) { create :group }
+ let_it_be(:current_user) { create(:admin) }
before do
sign_in(current_user)
+ gitlab_enable_admin_mode_sign_in(current_user)
stub_application_setting(default_group_visibility: internal)
end
@@ -25,6 +27,17 @@ RSpec.describe 'Admin Groups' do
end
describe 'create a group' do
+ describe 'with expected fields' do
+ it 'renders from as expected', :aggregate_failures do
+ visit new_admin_group_path
+
+ expect(page).to have_field('name')
+ expect(page).to have_field('group_path')
+ expect(page).to have_field('group_visibility_level_0')
+ expect(page).to have_field('description')
+ end
+ end
+
it 'creates new group' do
visit admin_groups_path
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index dfc7f5f6f84..0f6cba6c105 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe "Admin Health Check", :feature do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
describe '#show' do
diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb
index f4a70621cee..3f63bf9a15c 100644
--- a/spec/features/admin/admin_hook_logs_spec.rb
+++ b/spec/features/admin/admin_hook_logs_spec.rb
@@ -8,7 +8,9 @@ RSpec.describe 'Admin::HookLogs' do
let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
it 'show list of hook logs' do
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index 1c14d65a1cd..3fed402267c 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe 'Admin::Hooks' do
before do
sign_in(user)
+ gitlab_enable_admin_mode_sign_in(user)
end
describe 'GET /admin/hooks' do
diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb
index 35638e0829b..815a73b1450 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -7,7 +7,9 @@ RSpec.describe 'admin issues labels' do
let!(:feature_label) { Label.create(title: 'feature', template: true) }
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
describe 'list' do
diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb
index 7a9a6f2ccb8..e54837ede11 100644
--- a/spec/features/admin/admin_manage_applications_spec.rb
+++ b/spec/features/admin/admin_manage_applications_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
RSpec.describe 'admin manage applications' do
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
it 'creates new oauth application' do
diff --git a/spec/features/admin/admin_mode/login_spec.rb b/spec/features/admin/admin_mode/login_spec.rb
index 7cbba9ec674..f1dee075925 100644
--- a/spec/features/admin/admin_mode/login_spec.rb
+++ b/spec/features/admin/admin_mode/login_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Admin Mode Login', :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do
+RSpec.describe 'Admin Mode Login' do
include TermsHelper
include UserLoginHelper
include LdapHelpers
diff --git a/spec/features/admin/admin_mode/logout_spec.rb b/spec/features/admin/admin_mode/logout_spec.rb
index b4d49fe760f..b7fa59bbfb7 100644
--- a/spec/features/admin/admin_mode/logout_spec.rb
+++ b/spec/features/admin/admin_mode/logout_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Admin Mode Logout', :js, :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do
+RSpec.describe 'Admin Mode Logout', :js do
include TermsHelper
include UserLoginHelper
diff --git a/spec/features/admin/admin_mode/workers_spec.rb b/spec/features/admin/admin_mode/workers_spec.rb
index d037f5555dc..fbbcf19063b 100644
--- a/spec/features/admin/admin_mode/workers_spec.rb
+++ b/spec/features/admin/admin_mode/workers_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# Test an operation that triggers background jobs requiring administrative rights
-RSpec.describe 'Admin mode for workers', :do_not_mock_admin_mode, :request_store, :clean_gitlab_redis_shared_state do
+RSpec.describe 'Admin mode for workers', :request_store do
let(:user) { create(:user) }
let(:user_to_delete) { create(:user) }
diff --git a/spec/features/admin/admin_mode_spec.rb b/spec/features/admin/admin_mode_spec.rb
index 3b4edbc1a07..8169b3a20db 100644
--- a/spec/features/admin/admin_mode_spec.rb
+++ b/spec/features/admin/admin_mode_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Admin mode', :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do
+RSpec.describe 'Admin mode' do
include MobileHelpers
include StubENV
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index 522da760062..ff4e592234b 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -11,6 +11,7 @@ RSpec.describe "Admin::Projects" do
before do
sign_in(current_user)
+ gitlab_enable_admin_mode_sign_in(current_user)
end
describe "GET /admin/projects" do
diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb
index c649fdd8e19..e92528d431d 100644
--- a/spec/features/admin/admin_requests_profiles_spec.rb
+++ b/spec/features/admin/admin_requests_profiles_spec.rb
@@ -7,7 +7,9 @@ RSpec.describe 'Admin::RequestsProfilesController' do
before do
stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
after do
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 0e20ccf6bec..e16cde3fa1c 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -9,7 +9,9 @@ RSpec.describe "Admin Runners" do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
describe "Runners page" do
@@ -282,6 +284,12 @@ RSpec.describe "Admin Runners" do
visit admin_runner_path(runner)
end
+ describe 'runner page breadcrumbs' do
+ it 'contains the current runner’s short sha' do
+ expect(page.find('h2')).to have_content(runner.short_sha)
+ end
+ end
+
describe 'projects' do
it 'contains project names' do
expect(page).to have_content(@project1.full_name)
diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb
index d94889b825a..be781730924 100644
--- a/spec/features/admin/admin_sees_project_statistics_spec.rb
+++ b/spec/features/admin/admin_sees_project_statistics_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe "Admin > Admin sees project statistics" do
before do
sign_in(current_user)
+ gitlab_enable_admin_mode_sign_in(current_user)
visit admin_project_path(project)
end
@@ -15,7 +16,7 @@ RSpec.describe "Admin > Admin sees project statistics" do
let(:project) { create(:project, :repository) }
it "shows project statistics" do
- expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes / Snippets: 0 Bytes)")
+ expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes / Snippets: 0 Bytes / Packages: 0 Bytes / Uploads: 0 Bytes)")
end
end
diff --git a/spec/features/admin/admin_sees_projects_statistics_spec.rb b/spec/features/admin/admin_sees_projects_statistics_spec.rb
index 786fa98255c..2e96814d1e9 100644
--- a/spec/features/admin/admin_sees_projects_statistics_spec.rb
+++ b/spec/features/admin/admin_sees_projects_statistics_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe "Admin > Admin sees projects statistics" do
create(:project, :repository) { |project| project.statistics.destroy }
sign_in(current_user)
+ gitlab_enable_admin_mode_sign_in(current_user)
visit admin_projects_path
end
diff --git a/spec/features/admin/admin_serverless_domains_spec.rb b/spec/features/admin/admin_serverless_domains_spec.rb
index 256887f425f..0312e82e1ba 100644
--- a/spec/features/admin/admin_serverless_domains_spec.rb
+++ b/spec/features/admin/admin_serverless_domains_spec.rb
@@ -7,10 +7,12 @@ RSpec.describe 'Admin Serverless Domains', :js do
before do
allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
- it 'Add domain with certificate' do
+ it 'add domain with certificate' do
visit admin_serverless_domains_path
fill_in 'pages_domain[domain]', with: 'foo.com'
@@ -30,7 +32,7 @@ RSpec.describe 'Admin Serverless Domains', :js do
expect(page).to have_content '/CN=test-certificate'
end
- it 'Update domain certificate' do
+ it 'update domain certificate' do
visit admin_serverless_domains_path
fill_in 'pages_domain[domain]', with: 'foo.com'
@@ -60,7 +62,7 @@ RSpec.describe 'Admin Serverless Domains', :js do
context 'when domain exists' do
let!(:domain) { create(:pages_domain, :instance_serverless) }
- it 'Displays a modal when attempting to delete a domain' do
+ it 'displays a modal when attempting to delete a domain' do
visit admin_serverless_domains_path
click_button 'Delete domain'
@@ -71,7 +73,7 @@ RSpec.describe 'Admin Serverless Domains', :js do
end
end
- it 'Displays a modal with disabled button if unable to delete a domain' do
+ it 'displays a modal with disabled button if unable to delete a domain' do
create(:serverless_domain_cluster, pages_domain: domain)
visit admin_serverless_domains_path
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 8929abc7edc..06d31b544ea 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do
+RSpec.describe 'Admin updates settings' do
include StubENV
include TermsHelper
include UsageDataHelpers
@@ -24,7 +24,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
visit general_admin_application_settings_path
end
- it 'Change visibility settings' do
+ it 'change visibility settings' do
page.within('.as-visibility-access') do
choose "application_setting_default_project_visibility_20"
click_button 'Save changes'
@@ -33,7 +33,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(page).to have_content "Application settings saved successfully"
end
- it 'Uncheck all restricted visibility levels' do
+ it 'uncheck all restricted visibility levels' do
page.within('.as-visibility-access') do
find('#application_setting_visibility_level_0').set(false)
find('#application_setting_visibility_level_10').set(false)
@@ -47,7 +47,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(find('#application_setting_visibility_level_20')).not_to be_checked
end
- it 'Modify import sources' do
+ it 'modify import sources' do
expect(current_settings.import_sources).not_to be_empty
page.within('.as-visibility-access') do
@@ -70,7 +70,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(current_settings.import_sources).to eq(['git'])
end
- it 'Change Visibility and Access Controls' do
+ it 'change Visibility and Access Controls' do
page.within('.as-visibility-access') do
uncheck 'Project export enabled'
click_button 'Save changes'
@@ -80,7 +80,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(page).to have_content "Application settings saved successfully"
end
- it 'Change Keys settings' do
+ it 'change Keys settings' do
page.within('.as-visibility-access') do
select 'Are forbidden', from: 'RSA SSH keys'
select 'Are allowed', from: 'DSA SSH keys'
@@ -98,7 +98,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(find_field('ED25519 SSH keys').value).to eq(forbidden)
end
- it 'Change Account and Limit Settings' do
+ it 'change Account and Limit Settings' do
page.within('.as-account-limit') do
uncheck 'Gravatar enabled'
click_button 'Save changes'
@@ -108,7 +108,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(page).to have_content "Application settings saved successfully"
end
- it 'Change Maximum import size' do
+ it 'change Maximum import size' do
page.within('.as-account-limit') do
fill_in 'Maximum import size (MB)', with: 15
click_button 'Save changes'
@@ -118,7 +118,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(page).to have_content "Application settings saved successfully"
end
- it 'Change New users set to external', :js do
+ it 'change New users set to external', :js do
user_internal_regex = find('#application_setting_user_default_internal_regex', visible: :all)
expect(user_internal_regex).to be_readonly
@@ -144,7 +144,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
end
end
- it 'Change Sign-in restrictions' do
+ it 'change Sign-in restrictions' do
page.within('.as-signin') do
fill_in 'Home page URL', with: 'https://about.gitlab.com/'
click_button 'Save changes'
@@ -154,7 +154,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(page).to have_content "Application settings saved successfully"
end
- it 'Terms of Service' do
+ it 'terms of Service' do
# Already have the admin accept terms, so they don't need to accept in this spec.
_existing_terms = create(:term)
accept_terms(admin)
@@ -170,7 +170,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(page).to have_content 'Application settings saved successfully'
end
- it 'Modify oauth providers' do
+ it 'modify oauth providers' do
expect(current_settings.disabled_oauth_sign_in_sources).to be_empty
page.within('.as-signin') do
@@ -190,7 +190,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(current_settings.disabled_oauth_sign_in_sources).not_to include('google_oauth2')
end
- it 'Oauth providers do not raise validation errors when saving unrelated changes' do
+ it 'oauth providers do not raise validation errors when saving unrelated changes' do
expect(current_settings.disabled_oauth_sign_in_sources).to be_empty
page.within('.as-signin') do
@@ -213,7 +213,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(current_settings.disabled_oauth_sign_in_sources).to include('google_oauth2')
end
- it 'Configure web terminal' do
+ it 'configure web terminal' do
page.within('.as-terminal') do
fill_in 'Max session time', with: 15
click_button 'Save changes'
@@ -255,7 +255,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
visit general_admin_application_settings_path
end
- it 'Enable hiding third party offers' do
+ it 'enable hiding third party offers' do
page.within('.as-third-party-offers') do
check 'Do not display offers from third parties within GitLab'
click_button 'Save changes'
@@ -265,7 +265,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(current_settings.hide_third_party_offers).to be true
end
- it 'Change Slack Notifications Service template settings', :js do
+ it 'change Slack Notifications Service template settings', :js do
first(:link, 'Service Templates').click
click_link 'Slack notifications'
fill_in 'Webhook', with: 'http://localhost'
@@ -315,7 +315,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
end
context 'CI/CD page' do
- it 'Change CI/CD settings' do
+ it 'change CI/CD settings' do
visit ci_cd_admin_application_settings_path
page.within('.as-ci-cd') do
@@ -380,7 +380,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
end
context 'Repository page' do
- it 'Change Repository storage settings' do
+ it 'change Repository storage settings' do
visit repository_admin_application_settings_path
page.within('.as-repository-storage') do
@@ -393,7 +393,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
end
context 'Reporting page' do
- it 'Change Spam settings' do
+ it 'change Spam settings' do
visit reporting_admin_application_settings_path
page.within('.as-spam') do
@@ -421,7 +421,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
visit metrics_and_profiling_admin_application_settings_path
end
- it 'Change Prometheus settings' do
+ it 'change Prometheus settings' do
page.within('.as-prometheus') do
check 'Enable Prometheus Metrics'
click_button 'Save changes'
@@ -431,7 +431,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(page).to have_content "Application settings saved successfully"
end
- it 'Change Performance bar settings' do
+ it 'change Performance bar settings' do
group = create(:group)
page.within('.as-performance-bar') do
@@ -474,7 +474,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
end
context 'Network page' do
- it 'Changes Outbound requests settings' do
+ it 'changes Outbound requests settings' do
visit network_admin_application_settings_path
page.within('.as-outbound') do
@@ -492,7 +492,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(current_settings.dns_rebinding_protection_enabled).to be false
end
- it 'Changes Issues rate limits settings' do
+ it 'changes Issues rate limits settings' do
visit network_admin_application_settings_path
page.within('.as-issue-limits') do
@@ -510,7 +510,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
visit preferences_admin_application_settings_path
end
- it 'Change Help page' do
+ it 'change Help page' do
stub_feature_flags(help_page_documentation_redirect: true)
new_support_url = 'http://example.com/help'
@@ -531,7 +531,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(page).to have_content "Application settings saved successfully"
end
- it 'Change Pages settings' do
+ it 'change Pages settings' do
page.within('.as-pages') do
fill_in 'Maximum size of pages (MB)', with: 15
check 'Require users to prove ownership of custom domains'
@@ -543,7 +543,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
expect(page).to have_content "Application settings saved successfully"
end
- it 'Change Real-time features settings' do
+ it 'change Real-time features settings' do
page.within('.as-realtime') do
fill_in 'Polling interval multiplier', with: 5.0
click_button 'Save changes'
@@ -564,7 +564,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
.to have_content "The form contains the following error: Polling interval multiplier must be greater than or equal to 0"
end
- it "Change Pages Let's Encrypt settings" do
+ it "change Pages Let's Encrypt settings" do
visit preferences_admin_application_settings_path
page.within('.as-pages') do
fill_in 'Email', with: 'my@test.example.com'
@@ -578,7 +578,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
end
context 'Nav bar' do
- it 'Shows default help links in nav' do
+ it 'shows default help links in nav' do
default_support_url = 'https://about.gitlab.com/getting-help/'
visit root_dashboard_path
@@ -591,7 +591,7 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n
end
end
- it 'Shows custom support url in nav when set' do
+ it 'shows custom support url in nav when set' do
new_support_url = 'http://example.com/help'
stub_application_setting(help_page_support_url: new_support_url)
diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb
index 6a0448fd890..2225f25aa1e 100644
--- a/spec/features/admin/admin_system_info_spec.rb
+++ b/spec/features/admin/admin_system_info_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
RSpec.describe 'Admin System Info' do
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
describe 'GET /admin/system_info' do
diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
index ec3dd322f97..cae190e76b0 100644
--- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb
+++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe 'Admin > Users > Impersonation Tokens', :js do
before do
sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
describe "token creation" do
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index 0fb5124f673..0e448446085 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Admin uses repository checks', :request_store, :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do
+RSpec.describe 'Admin uses repository checks', :request_store do
include StubENV
let(:admin) { create(:admin) }
diff --git a/spec/features/admin/clusters/applications_spec.rb b/spec/features/admin/clusters/applications_spec.rb
index 3bcadfdbfc1..e083e4fee4c 100644
--- a/spec/features/admin/clusters/applications_spec.rb
+++ b/spec/features/admin/clusters/applications_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe 'Instance-level Cluster Applications', :js do
before do
sign_in(user)
+ gitlab_enable_admin_mode_sign_in(user)
end
describe 'Installing applications' do
diff --git a/spec/features/admin/clusters/eks_spec.rb b/spec/features/admin/clusters/eks_spec.rb
index ad7122bf182..a1bac720349 100644
--- a/spec/features/admin/clusters/eks_spec.rb
+++ b/spec/features/admin/clusters/eks_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe 'Instance-level AWS EKS Cluster', :js do
before do
sign_in(user)
+ gitlab_enable_admin_mode_sign_in(user)
end
context 'when user does not have a cluster and visits group clusters page' do
diff --git a/spec/features/admin/dashboard_spec.rb b/spec/features/admin/dashboard_spec.rb
index acb8fb54e11..c040811ada1 100644
--- a/spec/features/admin/dashboard_spec.rb
+++ b/spec/features/admin/dashboard_spec.rb
@@ -6,7 +6,9 @@ RSpec.describe 'admin visits dashboard' do
include ProjectForksHelper
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
context 'counting forks', :js do
diff --git a/spec/features/admin/services/admin_activates_prometheus_spec.rb b/spec/features/admin/services/admin_activates_prometheus_spec.rb
index 199eae59afc..a225de365c8 100644
--- a/spec/features/admin/services/admin_activates_prometheus_spec.rb
+++ b/spec/features/admin/services/admin_activates_prometheus_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe 'Admin activates Prometheus', :js do
before do
sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit(admin_application_settings_services_path)
diff --git a/spec/features/admin/services/admin_visits_service_templates_spec.rb b/spec/features/admin/services/admin_visits_service_templates_spec.rb
index a37e57304aa..563bca8b32f 100644
--- a/spec/features/admin/services/admin_visits_service_templates_spec.rb
+++ b/spec/features/admin/services/admin_visits_service_templates_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe 'Admin visits service templates' do
before do
sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit(admin_application_settings_services_path)
end
diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb
new file mode 100644
index 00000000000..e7dd50ed514
--- /dev/null
+++ b/spec/features/admin/users/user_spec.rb
@@ -0,0 +1,372 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Admin::Users::User' do
+ let_it_be(:user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
+ let_it_be(:current_user) { create(:admin, last_activity_on: 5.days.ago) }
+
+ before do
+ sign_in(current_user)
+ gitlab_enable_admin_mode_sign_in(current_user)
+ stub_feature_flags(vue_admin_users: false)
+ end
+
+ describe 'GET /admin/users/:id' do
+ it 'has user info', :aggregate_failures do
+ visit admin_users_path
+ click_link user.name
+
+ expect(page).to have_content(user.email)
+ expect(page).to have_content(user.name)
+ expect(page).to have_content("ID: #{user.id}")
+ expect(page).to have_content("Namespace ID: #{user.namespace_id}")
+ expect(page).to have_button('Deactivate user')
+ expect(page).to have_button('Block user')
+ expect(page).to have_button('Delete user')
+ expect(page).to have_button('Delete user and contributions')
+ end
+
+ context 'user pending approval' do
+ it 'shows user info', :aggregate_failures do
+ user = create(:user, :blocked_pending_approval)
+
+ visit admin_users_path
+ click_link 'Pending approval'
+ click_link user.name
+
+ expect(page).to have_content(user.name)
+ expect(page).to have_content('Pending approval')
+ expect(page).to have_link('Approve user')
+ expect(page).to have_link('Reject request')
+ end
+ end
+
+ context 'when blocking/unblocking the user' do
+ it 'shows confirmation and allows blocking and unblocking', :js do
+ visit admin_user_path(user)
+
+ find('button', text: 'Block user').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Block user')
+ expect(page).to have_content('You can always unblock their account, their data will remain intact.')
+
+ find('.modal-footer button', text: 'Block').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Successfully blocked')
+ expect(page).to have_content('This user is blocked')
+
+ find('button', text: 'Unblock user').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Unblock user')
+ expect(page).to have_content('You can always block their account again if needed.')
+
+ find('.modal-footer button', text: 'Unblock').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Successfully unblocked')
+ expect(page).to have_content('Block this user')
+ end
+ end
+
+ context 'when deactivating/re-activating the user' do
+ it 'shows confirmation and allows deactivating/re-activating', :js do
+ visit admin_user_path(user)
+
+ find('button', text: 'Deactivate user').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Deactivate user')
+ expect(page).to have_content('You can always re-activate their account, their data will remain intact.')
+
+ find('.modal-footer button', text: 'Deactivate').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Successfully deactivated')
+ expect(page).to have_content('Reactivate this user')
+
+ find('button', text: 'Activate user').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Activate user')
+ expect(page).to have_content('You can always deactivate their account again if needed.')
+
+ find('.modal-footer button', text: 'Activate').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Successfully activated')
+ expect(page).to have_content('Deactivate this user')
+ end
+ end
+
+ describe 'Impersonation' do
+ let_it_be(:another_user) { create(:user) }
+
+ context 'before impersonating' do
+ subject { visit admin_user_path(user_to_visit) }
+
+ let(:user_to_visit) { another_user }
+
+ context 'for other users' do
+ it 'shows impersonate button for other users' do
+ subject
+
+ expect(page).to have_content('Impersonate')
+ end
+ end
+
+ context 'for admin itself' do
+ let(:user_to_visit) { current_user }
+
+ it 'does not show impersonate button for admin itself' do
+ subject
+
+ expect(page).not_to have_content('Impersonate')
+ end
+ end
+
+ context 'for blocked user' do
+ before do
+ another_user.block
+ end
+
+ it 'does not show impersonate button for blocked user' do
+ subject
+
+ expect(page).not_to have_content('Impersonate')
+ end
+ end
+
+ context 'when impersonation is disabled' do
+ before do
+ stub_config_setting(impersonation_enabled: false)
+ end
+
+ it 'does not show impersonate button' do
+ subject
+
+ expect(page).not_to have_content('Impersonate')
+ end
+ end
+ end
+
+ context 'when impersonating' do
+ subject { click_link 'Impersonate' }
+
+ before do
+ visit admin_user_path(another_user)
+ end
+
+ it 'logs in as the user when impersonate is clicked' do
+ subject
+
+ expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username)
+ end
+
+ it 'sees impersonation log out icon' do
+ subject
+
+ icon = first('[data-testid="incognito-icon"]')
+ expect(icon).not_to be nil
+ end
+
+ context 'a user with an expired password' do
+ before do
+ another_user.update!(password_expires_at: Time.now - 5.minutes)
+ end
+
+ it 'does not redirect to password change page' do
+ subject
+
+ expect(current_path).to eq('/')
+ end
+ end
+ end
+
+ context 'ending impersonation' do
+ subject { find(:css, 'li.impersonation a').click }
+
+ before do
+ visit admin_user_path(another_user)
+ click_link 'Impersonate'
+ end
+
+ it 'logs out of impersonated user back to original user' do
+ subject
+
+ expect(page.find(:css, '.header-user .profile-link')['data-user']).to eq(current_user.username)
+ end
+
+ it 'is redirected back to the impersonated users page in the admin after stopping' do
+ subject
+
+ expect(current_path).to eq("/admin/users/#{another_user.username}")
+ end
+
+ context 'a user with an expired password' do
+ before do
+ another_user.update!(password_expires_at: Time.now - 5.minutes)
+ end
+
+ it 'is redirected back to the impersonated users page in the admin after stopping' do
+ subject
+
+ expect(current_path).to eq("/admin/users/#{another_user.username}")
+ end
+ end
+ end
+ end
+
+ describe 'Two-factor Authentication status' do
+ it 'shows when enabled' do
+ user.update!(otp_required_for_login: true)
+
+ visit admin_user_path(user)
+
+ expect_two_factor_status('Enabled')
+ end
+
+ it 'shows when disabled' do
+ visit admin_user_path(user)
+
+ expect_two_factor_status('Disabled')
+ end
+
+ def expect_two_factor_status(status)
+ page.within('.two-factor-status') do
+ expect(page).to have_content(status)
+ end
+ end
+ end
+
+ describe 'Email verification status' do
+ let!(:secondary_email) do
+ create :email, email: 'secondary@example.com', user: user
+ end
+
+ it 'displays the correct status for an unverified email address', :aggregate_failures do
+ user.update!(confirmed_at: nil, unconfirmed_email: user.email)
+ visit admin_user_path(user)
+
+ expect(page).to have_content("#{user.email} Unverified")
+ expect(page).to have_content("#{secondary_email.email} Unverified")
+ end
+
+ it 'displays the correct status for a verified email address' do
+ visit admin_user_path(user)
+ expect(page).to have_content("#{user.email} Verified")
+
+ secondary_email.confirm
+ expect(secondary_email.confirmed?).to be_truthy
+
+ visit admin_user_path(user)
+ expect(page).to have_content("#{secondary_email.email} Verified")
+ end
+ end
+ end
+
+ describe 'show user attributes' do
+ it 'has expected attributes', :aggregate_failures do
+ visit admin_users_path
+
+ click_link user.name
+
+ expect(page).to have_content 'Account'
+ expect(page).to have_content 'Personal projects limit'
+ end
+ end
+
+ describe 'remove users secondary email', :js do
+ let!(:secondary_email) do
+ create :email, email: 'secondary@example.com', user: user
+ end
+
+ it do
+ visit admin_user_path(user.username)
+
+ expect(page).to have_content("Secondary email: #{secondary_email.email}")
+
+ accept_confirm { find("#remove_email_#{secondary_email.id}").click }
+
+ expect(page).not_to have_content(secondary_email.email)
+ end
+ end
+
+ describe 'show user keys', :js do
+ it do
+ key1 = create(:key, user: user, title: 'ssh-rsa Key1', key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1')
+ key2 = create(:key, user: user, title: 'ssh-rsa Key2', key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2')
+
+ visit admin_users_path
+
+ click_link user.name
+ click_link 'SSH keys'
+
+ expect(page).to have_content(key1.title)
+ expect(page).to have_content(key2.title)
+
+ click_link key2.title
+
+ expect(page).to have_content(key2.title)
+ expect(page).to have_content(key2.key)
+
+ click_button 'Delete'
+
+ page.within('.modal') do
+ page.click_button('Delete')
+ end
+
+ expect(page).not_to have_content(key2.title)
+ end
+ end
+
+ describe 'show user identities' do
+ it 'shows user identities', :aggregate_failures do
+ visit admin_user_identities_path(user)
+
+ expect(page).to have_content(user.name)
+ expect(page).to have_content('twitter')
+ end
+ end
+
+ describe 'update user identities' do
+ before do
+ allow(Gitlab::Auth::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated])
+ end
+
+ it 'modifies twitter identity', :aggregate_failures do
+ visit admin_user_identities_path(user)
+
+ find('.table').find(:link, 'Edit').click
+ fill_in 'identity_extern_uid', with: '654321'
+ select 'twitter_updated', from: 'identity_provider'
+ click_button 'Save changes'
+
+ expect(page).to have_content(user.name)
+ expect(page).to have_content('twitter_updated')
+ expect(page).to have_content('654321')
+ end
+ end
+
+ describe 'remove user with identities' do
+ it 'removes user with twitter identity', :aggregate_failures do
+ visit admin_user_identities_path(user)
+
+ click_link 'Delete'
+
+ expect(page).to have_content(user.name)
+ expect(page).not_to have_content('twitter')
+ end
+ end
+end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/users/users_spec.rb
index 97a30143a59..9482b4f8603 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/users/users_spec.rb
@@ -2,21 +2,20 @@
require 'spec_helper'
-RSpec.describe "Admin::Users" do
+RSpec.describe 'Admin::Users' do
include Spec::Support::Helpers::Features::ResponsiveTableHelpers
- let!(:user) do
- create(:omniauth_user, provider: 'twitter', extern_uid: '123456')
- end
-
- let!(:current_user) { create(:admin, last_activity_on: 5.days.ago) }
+ let_it_be(:user, reload: true) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
+ let_it_be(:current_user) { create(:admin, last_activity_on: 5.days.ago) }
before do
sign_in(current_user)
+ gitlab_enable_admin_mode_sign_in(current_user)
end
- describe "GET /admin/users" do
+ describe 'GET /admin/users' do
before do
+ stub_feature_flags(vue_admin_users: false)
visit admin_users_path
end
@@ -27,8 +26,8 @@ RSpec.describe "Admin::Users" do
it "has users list" do
expect(page).to have_content(current_user.email)
expect(page).to have_content(current_user.name)
- expect(page).to have_content(current_user.created_at.strftime("%e %b, %Y"))
- expect(page).to have_content(current_user.last_activity_on.strftime("%e %b, %Y"))
+ expect(page).to have_content(current_user.created_at.strftime('%e %b, %Y'))
+ expect(page).to have_content(current_user.last_activity_on.strftime('%e %b, %Y'))
expect(page).to have_content(user.email)
expect(page).to have_content(user.name)
expect(page).to have_content('Projects')
@@ -38,7 +37,7 @@ RSpec.describe "Admin::Users" do
expect(page).to have_button('Delete user and contributions')
end
- describe "view extra user information" do
+ describe 'view extra user information' do
it 'shows the user popover on hover', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/11290' do
expect(page).not_to have_selector('#__BV_popover_1__')
@@ -86,7 +85,7 @@ RSpec.describe "Admin::Users" do
end
describe 'search and sort' do
- before do
+ before_all do
create(:user, name: 'Foo Bar', last_activity_on: 3.days.ago)
create(:user, name: 'Foo Baz', last_activity_on: 2.days.ago)
create(:user, name: 'Dmitriy')
@@ -205,15 +204,11 @@ RSpec.describe "Admin::Users" do
end
end
- context 'when blocking a user' do
- it 'shows confirmation and allows blocking', :js do
+ context 'when blocking/unblocking a user' do
+ it 'shows confirmation and allows blocking and unblocking', :js do
expect(page).to have_content(user.email)
- find("[data-testid='user-action-button-#{user.id}']").click
-
- within find("[data-testid='user-action-dropdown-#{user.id}']") do
- find('li button', text: 'Block').click
- end
+ click_action_in_user_dropdown(user.id, 'Block')
wait_for_requests
@@ -228,26 +223,92 @@ RSpec.describe "Admin::Users" do
expect(page).to have_content('Successfully blocked')
expect(page).not_to have_content(user.email)
+
+ click_link 'Blocked'
+
+ wait_for_requests
+
+ expect(page).to have_content(user.email)
+
+ click_action_in_user_dropdown(user.id, 'Unblock')
+
+ expect(page).to have_content('Unblock user')
+ expect(page).to have_content('You can always block their account again if needed.')
+
+ find('.modal-footer button', text: 'Unblock').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Successfully unblocked')
+ expect(page).not_to have_content(user.email)
+ end
+ end
+
+ context 'when deactivating/re-activating a user' do
+ it 'shows confirmation and allows deactivating and re-activating', :js do
+ expect(page).to have_content(user.email)
+
+ click_action_in_user_dropdown(user.id, 'Deactivate')
+
+ expect(page).to have_content('Deactivate user')
+ expect(page).to have_content('Deactivating a user has the following effects')
+ expect(page).to have_content('The user will be logged out')
+ expect(page).to have_content('Personal projects, group and user history will be left intact')
+
+ find('.modal-footer button', text: 'Deactivate').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Successfully deactivated')
+ expect(page).not_to have_content(user.email)
+
+ click_link 'Deactivated'
+
+ wait_for_requests
+
+ expect(page).to have_content(user.email)
+
+ click_action_in_user_dropdown(user.id, 'Activate')
+
+ expect(page).to have_content('Activate user')
+ expect(page).to have_content('You can always deactivate their account again if needed.')
+
+ find('.modal-footer button', text: 'Activate').click
+
+ wait_for_requests
+
+ expect(page).to have_content('Successfully activated')
+ expect(page).not_to have_content(user.email)
end
end
+
+ def click_action_in_user_dropdown(user_id, action)
+ find("[data-testid='user-action-button-#{user_id}']").click
+
+ within find("[data-testid='user-action-dropdown-#{user_id}']") do
+ find('li button', text: action).click
+ end
+
+ wait_for_requests
+ end
end
- describe "GET /admin/users/new" do
+ describe 'GET /admin/users/new' do
let(:user_username) { 'bang' }
before do
visit new_admin_user_path
- fill_in "user_name", with: "Big Bang"
- fill_in "user_username", with: user_username
- fill_in "user_email", with: "bigbang@mail.com"
+ fill_in 'user_name', with: 'Big Bang'
+ fill_in 'user_username', with: user_username
+ fill_in 'user_email', with: 'bigbang@mail.com'
end
- it "creates new user" do
- expect { click_button "Create user" }.to change {User.count}.by(1)
+ it 'creates new user' do
+ expect { click_button 'Create user' }.to change {User.count}.by(1)
end
- it "applies defaults to user" do
- click_button "Create user"
+ it 'applies defaults to user' do
+ click_button 'Create user'
user = User.find_by(username: 'bang')
expect(user.projects_limit)
.to eq(Gitlab.config.gitlab.default_projects_limit)
@@ -255,24 +316,24 @@ RSpec.describe "Admin::Users" do
.to eq(Gitlab.config.gitlab.default_can_create_group)
end
- it "creates user with valid data" do
- click_button "Create user"
+ it 'creates user with valid data' do
+ click_button 'Create user'
user = User.find_by(username: 'bang')
expect(user.name).to eq('Big Bang')
expect(user.email).to eq('bigbang@mail.com')
end
- it "calls send mail" do
+ it 'calls send mail' do
expect_next_instance_of(NotificationService) do |instance|
expect(instance).to receive(:new_user)
end
- click_button "Create user"
+ click_button 'Create user'
end
- it "sends valid email to user with email & password" do
+ it 'sends valid email to user with email & password' do
perform_enqueued_jobs do
- click_button "Create user"
+ click_button 'Create user'
end
user = User.find_by(username: 'bang')
@@ -286,7 +347,7 @@ RSpec.describe "Admin::Users" do
let(:user_username) { 'Bing bang' }
it "doesn't create the user and shows an error message" do
- expect { click_button "Create user" }.to change {User.count}.by(0)
+ expect { click_button 'Create user' }.to change {User.count}.by(0)
expect(page).to have_content('The form contains the following error')
expect(page).to have_content('Username can contain only letters, digits')
@@ -356,252 +417,34 @@ RSpec.describe "Admin::Users" do
end
end
- describe "GET /admin/users/:id" do
- it "has user info" do
- visit admin_users_path
- click_link user.name
-
- expect(page).to have_content(user.email)
- expect(page).to have_content(user.name)
- expect(page).to have_content("ID: #{user.id}")
- expect(page).to have_content("Namespace ID: #{user.namespace_id}")
- expect(page).to have_button('Deactivate user')
- expect(page).to have_button('Block user')
- expect(page).to have_button('Delete user')
- expect(page).to have_button('Delete user and contributions')
- end
-
- context 'user pending approval' do
- it 'shows user info' do
- user = create(:user, :blocked_pending_approval)
-
- visit admin_users_path
- click_link 'Pending approval'
- click_link user.name
-
- expect(page).to have_content(user.name)
- expect(page).to have_content('Pending approval')
- expect(page).to have_link('Approve user')
- expect(page).to have_button('Block user')
- expect(page).to have_button('Delete user')
- expect(page).to have_button('Delete user and contributions')
- end
- end
-
- context 'when blocking the user' do
- it 'shows confirmation and allows blocking', :js do
- visit admin_user_path(user)
-
- find('button', text: 'Block user').click
-
- wait_for_requests
-
- expect(page).to have_content('Block user')
- expect(page).to have_content('You can always unblock their account, their data will remain intact.')
-
- find('.modal-footer button', text: 'Block').click
-
- wait_for_requests
-
- expect(page).to have_content('Successfully blocked')
- expect(page).to have_content('This user is blocked')
- end
- end
-
- describe 'Impersonation' do
- let(:another_user) { create(:user) }
-
- context 'before impersonating' do
- subject { visit admin_user_path(user_to_visit) }
-
- let(:user_to_visit) { another_user }
-
- context 'for other users' do
- it 'shows impersonate button for other users' do
- subject
-
- expect(page).to have_content('Impersonate')
- end
- end
-
- context 'for admin itself' do
- let(:user_to_visit) { current_user }
-
- it 'does not show impersonate button for admin itself' do
- subject
-
- expect(page).not_to have_content('Impersonate')
- end
- end
-
- context 'for blocked user' do
- before do
- another_user.block
- end
-
- it 'does not show impersonate button for blocked user' do
- subject
-
- expect(page).not_to have_content('Impersonate')
- end
- end
-
- context 'when impersonation is disabled' do
- before do
- stub_config_setting(impersonation_enabled: false)
- end
-
- it 'does not show impersonate button' do
- subject
-
- expect(page).not_to have_content('Impersonate')
- end
- end
- end
-
- context 'when impersonating' do
- subject { click_link 'Impersonate' }
-
- before do
- visit admin_user_path(another_user)
- end
-
- it 'logs in as the user when impersonate is clicked' do
- subject
-
- expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username)
- end
-
- it 'sees impersonation log out icon' do
- subject
-
- icon = first('[data-testid="incognito-icon"]')
- expect(icon).not_to be nil
- end
-
- context 'a user with an expired password' do
- before do
- another_user.update(password_expires_at: Time.now - 5.minutes)
- end
-
- it 'does not redirect to password change page' do
- subject
-
- expect(current_path).to eq('/')
- end
- end
- end
-
- context 'ending impersonation' do
- subject { find(:css, 'li.impersonation a').click }
-
- before do
- visit admin_user_path(another_user)
- click_link 'Impersonate'
- end
-
- it 'logs out of impersonated user back to original user' do
- subject
-
- expect(page.find(:css, '.header-user .profile-link')['data-user']).to eq(current_user.username)
- end
-
- it 'is redirected back to the impersonated users page in the admin after stopping' do
- subject
-
- expect(current_path).to eq("/admin/users/#{another_user.username}")
- end
-
- context 'a user with an expired password' do
- before do
- another_user.update(password_expires_at: Time.now - 5.minutes)
- end
-
- it 'is redirected back to the impersonated users page in the admin after stopping' do
- subject
-
- expect(current_path).to eq("/admin/users/#{another_user.username}")
- end
- end
- end
- end
-
- describe 'Two-factor Authentication status' do
- it 'shows when enabled' do
- user.update_attribute(:otp_required_for_login, true)
-
- visit admin_user_path(user)
-
- expect_two_factor_status('Enabled')
- end
-
- it 'shows when disabled' do
- visit admin_user_path(user)
-
- expect_two_factor_status('Disabled')
- end
-
- def expect_two_factor_status(status)
- page.within('.two-factor-status') do
- expect(page).to have_content(status)
- end
- end
- end
-
- describe 'Email verification status' do
- let!(:secondary_email) do
- create :email, email: 'secondary@example.com', user: user
- end
-
- it 'displays the correct status for an unverified email address' do
- user.update(confirmed_at: nil, unconfirmed_email: user.email)
- visit admin_user_path(user)
-
- expect(page).to have_content("#{user.email} Unverified")
-
- expect(page).to have_content("#{secondary_email.email} Unverified")
- end
-
- it 'displays the correct status for a verified email address' do
- visit admin_user_path(user)
- expect(page).to have_content("#{user.email} Verified")
-
- secondary_email.confirm
- expect(secondary_email.confirmed?).to be_truthy
-
- visit admin_user_path(user)
- expect(page).to have_content("#{secondary_email.email} Verified")
- end
- end
- end
-
- describe "GET /admin/users/:id/edit" do
+ describe 'GET /admin/users/:id/edit' do
before do
+ stub_feature_flags(vue_admin_users: false)
visit admin_users_path
click_link "edit_user_#{user.id}"
end
- it "has user edit page" do
+ it 'has user edit page' do
expect(page).to have_content('Name')
expect(page).to have_content('Password')
end
- describe "Update user" do
+ describe 'Update user' do
before do
- fill_in "user_name", with: "Big Bang"
- fill_in "user_email", with: "bigbang@mail.com"
- fill_in "user_password", with: "AValidPassword1"
- fill_in "user_password_confirmation", with: "AValidPassword1"
- choose "user_access_level_admin"
- click_button "Save changes"
+ fill_in 'user_name', with: 'Big Bang'
+ fill_in 'user_email', with: 'bigbang@mail.com'
+ fill_in 'user_password', with: 'AValidPassword1'
+ fill_in 'user_password_confirmation', with: 'AValidPassword1'
+ choose 'user_access_level_admin'
+ click_button 'Save changes'
end
- it "shows page with new data" do
+ it 'shows page with new data' do
expect(page).to have_content('bigbang@mail.com')
expect(page).to have_content('Big Bang')
end
- it "changes user entry" do
+ it 'changes user entry' do
user.reload
expect(user.name).to eq('Big Bang')
expect(user.admin?).to be_truthy
@@ -623,9 +466,9 @@ RSpec.describe "Admin::Users" do
end
end
- describe "GET /admin/users/:id/projects" do
- let(:group) { create(:group) }
- let!(:project) { create(:project, group: group) }
+ describe 'GET /admin/users/:id/projects' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
before do
group.add_developer(user)
@@ -633,7 +476,7 @@ RSpec.describe "Admin::Users" do
visit projects_admin_user_path(user)
end
- it "lists group projects" do
+ it 'lists group projects' do
within(:css, '.gl-mb-3 + .card') do
expect(page).to have_content 'Group projects'
expect(page).to have_link group.name, href: admin_group_path(group)
@@ -690,112 +533,13 @@ RSpec.describe "Admin::Users" do
visit new_admin_user_identity_path(user)
- check_breadcrumb("New Identity")
+ check_breadcrumb('New Identity')
visit admin_user_identities_path(user)
find('.table').find(:link, 'Edit').click
- check_breadcrumb("Edit Identity")
- end
- end
-
- describe 'show user attributes' do
- it do
- visit admin_users_path
-
- click_link user.name
-
- expect(page).to have_content 'Account'
- expect(page).to have_content 'Personal projects limit'
- end
- end
-
- describe 'remove users secondary email', :js do
- let!(:secondary_email) do
- create :email, email: 'secondary@example.com', user: user
- end
-
- it do
- visit admin_user_path(user.username)
-
- expect(page).to have_content("Secondary email: #{secondary_email.email}")
-
- accept_confirm { find("#remove_email_#{secondary_email.id}").click }
-
- expect(page).not_to have_content(secondary_email.email)
- end
- end
-
- describe 'show user keys', :js do
- let!(:key1) do
- create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
- end
-
- let!(:key2) do
- create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2")
- end
-
- it do
- visit admin_users_path
-
- click_link user.name
- click_link 'SSH keys'
-
- expect(page).to have_content(key1.title)
- expect(page).to have_content(key2.title)
-
- click_link key2.title
-
- expect(page).to have_content(key2.title)
- expect(page).to have_content(key2.key)
-
- click_button 'Delete'
-
- page.within('.modal') do
- page.click_button('Delete')
- end
-
- expect(page).not_to have_content(key2.title)
- end
- end
-
- describe 'show user identities' do
- it 'shows user identities' do
- visit admin_user_identities_path(user)
-
- expect(page).to have_content(user.name)
- expect(page).to have_content('twitter')
- end
- end
-
- describe 'update user identities' do
- before do
- allow(Gitlab::Auth::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated])
- end
-
- it 'modifies twitter identity' do
- visit admin_user_identities_path(user)
-
- find('.table').find(:link, 'Edit').click
- fill_in 'identity_extern_uid', with: '654321'
- select 'twitter_updated', from: 'identity_provider'
- click_button 'Save changes'
-
- expect(page).to have_content(user.name)
- expect(page).to have_content('twitter_updated')
- expect(page).to have_content('654321')
- end
- end
-
- describe 'remove user with identities' do
- it 'removes user with twitter identity' do
- visit admin_user_identities_path(user)
-
- click_link 'Delete'
-
- expect(page).to have_content(user.name)
- expect(page).not_to have_content('twitter')
+ check_breadcrumb('Edit Identity')
end
end
diff --git a/spec/features/alert_management/alert_management_list_spec.rb b/spec/features/alert_management/alert_management_list_spec.rb
index 37658f8c545..44ed2f3d60c 100644
--- a/spec/features/alert_management/alert_management_list_spec.rb
+++ b/spec/features/alert_management/alert_management_list_spec.rb
@@ -5,54 +5,54 @@ require 'spec_helper'
RSpec.describe 'Alert Management index', :js do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
- let_it_be(:alert) { create(:alert_management_alert, project: project, status: 'triggered') }
before_all do
project.add_developer(developer)
end
- before do
- sign_in(developer)
+ context 'when a developer displays the alert list' do
+ before do
+ sign_in(developer)
- visit project_alert_management_index_path(project)
- wait_for_requests
- end
-
- context 'when a developer displays the alert list and alert integrations are not enabled' do
- it 'shows the alert page title' do
- expect(page).to have_content('Alerts')
+ visit project_alert_management_index_path(project)
+ wait_for_requests
end
- it 'shows the empty state by default' do
+ it 'shows the alert page title and empty state without filtered search or alert table' do
+ expect(page).to have_content('Alerts')
expect(page).to have_content('Surface alerts in GitLab')
- end
-
- it 'does not show the filtered search' do
+ expect(page).not_to have_selector('.gl-table')
page.within('.layout-page') do
expect(page).not_to have_css('[data-testid="search-icon"]')
end
end
- it 'does not show the alert table' do
- expect(page).not_to have_selector('.gl-table')
+ shared_examples 'alert page with title, filtered search, and table' do
+ it 'renders correctly' do
+ expect(page).to have_content('Alerts')
+ expect(page).to have_selector('.gl-table')
+ page.within('.layout-page') do
+ expect(page).to have_css('[data-testid="search-icon"]')
+ end
+ end
end
- end
- context 'when a developer displays the alert list and an HTTP integration is enabled' do
- let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
+ context 'when alerts have already been created' do
+ let_it_be(:alert) { create(:alert_management_alert, project: project) }
- it 'shows the alert page title' do
- expect(page).to have_content('Alerts')
+ it_behaves_like 'alert page with title, filtered search, and table'
end
- it 'shows the filtered search' do
- page.within('.layout-page') do
- expect(page).to have_css('[data-testid="search-icon"]')
- end
+ context 'when an HTTP integration is enabled' do
+ let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
+
+ it_behaves_like 'alert page with title, filtered search, and table'
end
- it 'shows the alert table' do
- expect(page).to have_selector('.gl-table')
+ context 'when the prometheus integration is enabled' do
+ let_it_be(:integration) { create(:prometheus_service, project: project) }
+
+ it_behaves_like 'alert page with title, filtered search, and table'
end
end
end
diff --git a/spec/features/alerts_settings/user_views_alerts_settings_spec.rb b/spec/features/alerts_settings/user_views_alerts_settings_spec.rb
index 0ded13ae607..698a36d3f76 100644
--- a/spec/features/alerts_settings/user_views_alerts_settings_spec.rb
+++ b/spec/features/alerts_settings/user_views_alerts_settings_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe 'Alert integrations settings form', :js do
end
describe 'when viewing alert integrations as a maintainer' do
- context 'with feature flag enabled' do
+ context 'with the default page permissions' do
before do
visit project_settings_operations_path(project, anchor: 'js-alert-management-settings')
wait_for_requests
@@ -33,19 +33,6 @@ RSpec.describe 'Alert integrations settings form', :js do
expect(page).to have_content('1. Select integration type')
end
end
-
- context 'with feature flag disabled' do
- before do
- stub_feature_flags(http_integrations_list: false)
-
- visit project_settings_operations_path(project, anchor: 'js-alert-management-settings')
- wait_for_requests
- end
-
- it 'shows the old alerts setting form' do
- expect(page).to have_content('Webhook URL')
- end
- end
end
describe 'when viewing alert integrations as a developer' do
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index f941adca233..8d0fa3e023b 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -37,6 +37,10 @@ RSpec.describe 'Issue Boards add issue modal', :js do
end
context 'modal interaction' do
+ before do
+ stub_feature_flags(add_issues_button: true)
+ end
+
it 'opens modal' do
click_button('Add issues')
@@ -72,6 +76,7 @@ RSpec.describe 'Issue Boards add issue modal', :js do
context 'issues list' do
before do
+ stub_feature_flags(add_issues_button: true)
click_button('Add issues')
wait_for_requests
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 06ec4e05828..b3cc2eb418d 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -27,11 +27,11 @@ RSpec.describe 'Issue Boards', :js do
end
it 'creates default lists' do
- lists = ['Open', 'To Do', 'Doing', 'Closed']
+ lists = %w[Open Closed]
wait_for_requests
- expect(page).to have_selector('.board', count: 4)
+ expect(page).to have_selector('.board', count: 2)
page.all('.board').each_with_index do |list, i|
expect(list.find('.board-title')).to have_content(lists[i])
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index f51b4d21e3b..cefb486349d 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -9,7 +9,9 @@ RSpec.describe 'Issue Boards shortcut', :js do
before do
create(:board, project: project)
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit project_path(project)
end
@@ -26,7 +28,9 @@ RSpec.describe 'Issue Boards shortcut', :js do
let(:project) { create(:project, :issues_disabled) }
before do
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit project_path(project)
end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 332c90df6d7..2af5b787a78 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -72,7 +72,7 @@ RSpec.describe 'Issue Boards', :js do
end
end
- it 'removes card from board when clicking ' do
+ it 'removes card from board when clicking' do
click_card(card)
page.within('.issue-boards-sidebar') do
diff --git a/spec/features/breadcrumbs_schema_markup_spec.rb b/spec/features/breadcrumbs_schema_markup_spec.rb
index 30d5f40fea8..a87a3d284de 100644
--- a/spec/features/breadcrumbs_schema_markup_spec.rb
+++ b/spec/features/breadcrumbs_schema_markup_spec.rb
@@ -15,15 +15,12 @@ RSpec.describe 'Breadcrumbs schema markup', :aggregate_failures do
item_list = get_schema_content
- expect(item_list.size).to eq 3
+ expect(item_list.size).to eq 2
expect(item_list[0]['name']).to eq project.namespace.name
expect(item_list[0]['item']).to eq user_url(project.owner)
expect(item_list[1]['name']).to eq project.name
expect(item_list[1]['item']).to eq project_url(project)
-
- expect(item_list[2]['name']).to eq 'Details'
- expect(item_list[2]['item']).to eq project_url(project)
end
it 'generates the breadcrumb schema for group projects' do
@@ -31,7 +28,7 @@ RSpec.describe 'Breadcrumbs schema markup', :aggregate_failures do
item_list = get_schema_content
- expect(item_list.size).to eq 4
+ expect(item_list.size).to eq 3
expect(item_list[0]['name']).to eq group.name
expect(item_list[0]['item']).to eq group_url(group)
@@ -40,9 +37,6 @@ RSpec.describe 'Breadcrumbs schema markup', :aggregate_failures do
expect(item_list[2]['name']).to eq group_project.name
expect(item_list[2]['item']).to eq project_url(group_project)
-
- expect(item_list[3]['name']).to eq 'Details'
- expect(item_list[3]['item']).to eq project_url(group_project)
end
it 'generates the breadcrumb schema for group' do
@@ -50,15 +44,12 @@ RSpec.describe 'Breadcrumbs schema markup', :aggregate_failures do
item_list = get_schema_content
- expect(item_list.size).to eq 3
+ expect(item_list.size).to eq 2
expect(item_list[0]['name']).to eq group.name
expect(item_list[0]['item']).to eq group_url(group)
expect(item_list[1]['name']).to eq subgroup.name
expect(item_list[1]['item']).to eq group_url(subgroup)
-
- expect(item_list[2]['name']).to eq 'Details'
- expect(item_list[2]['item']).to eq group_url(subgroup)
end
it 'generates the breadcrumb schema for issues' do
diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb
index 31d6bcda9e8..6fe6c099d80 100644
--- a/spec/features/clusters/cluster_detail_page_spec.rb
+++ b/spec/features/clusters/cluster_detail_page_spec.rb
@@ -168,6 +168,10 @@ RSpec.describe 'Clusterable > Show page' do
let(:cluster_path) { admin_cluster_path(cluster) }
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
+ before do
+ gitlab_enable_admin_mode_sign_in(current_user)
+ end
+
it_behaves_like 'show page' do
let(:cluster_type_label) { 'Instance cluster' }
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 97ee891dbb8..f8e84043c1b 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -125,7 +125,7 @@ RSpec.describe 'Commits' do
visit pipeline_path(pipeline)
end
- it 'Renders header', :js do
+ it 'renders header', :js do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message.gsub!(/\s+/, ' ')
expect(page).to have_content pipeline.user.name
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index b63079777cb..233a93c2054 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -24,10 +24,6 @@ RSpec.describe 'Value Stream Analytics', :js do
wait_for_requests
end
- it 'shows introductory message' do
- expect(page).to have_content('Introducing Value Stream Analytics')
- end
-
it 'shows pipeline summary' do
expect(new_issues_counter).to have_content('-')
expect(commits_counter).to have_content('-')
@@ -48,6 +44,16 @@ RSpec.describe 'Value Stream Analytics', :js do
@build = create_cycle(user, project, issue, mr, milestone, pipeline)
deploy_master(user, project)
+ issue.metrics.update!(first_mentioned_in_commit_at: issue.metrics.first_associated_with_milestone_at + 1.day)
+ merge_request = issue.merge_requests_closing_issues.first.merge_request
+ merge_request.update!(created_at: issue.metrics.first_associated_with_milestone_at + 1.day)
+ merge_request.metrics.update!(
+ latest_build_started_at: 4.hours.ago,
+ latest_build_finished_at: 3.hours.ago,
+ merged_at: merge_request.created_at + 1.hour,
+ first_deployed_to_production_at: merge_request.created_at + 2.hours
+ )
+
sign_in(user)
visit project_cycle_analytics_path(project)
end
diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb
index 9965229a539..1b349fa2276 100644
--- a/spec/features/dashboard/archived_projects_spec.rb
+++ b/spec/features/dashboard/archived_projects_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe 'Dashboard Archived Project' do
end
it 'searches archived projects', :js do
- click_button 'Last updated'
+ click_button 'Name'
click_link 'Show archived projects'
expect(page).to have_link(project.name)
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index 653a16bbbcc..0b99fed2a2d 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -17,14 +17,11 @@ RSpec.describe 'Dashboard Group' do
visit dashboard_groups_path
find('.btn-success').click
new_name = 'Samurai'
- new_description = 'Tokugawa Shogunate'
fill_in 'group_name', with: new_name
- fill_in 'group_description', with: new_description
click_button 'Create group'
expect(current_path).to eq group_path(Group.find_by(name: new_name))
expect(page).to have_content(new_name)
- expect(page).to have_content(new_description)
end
end
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 952a78ec79a..0e76b5478a1 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -52,20 +52,29 @@ RSpec.describe 'Dashboard Merge Requests' do
end
context 'merge requests exist' do
+ let_it_be(:author_user) { create(:user) }
let(:label) { create(:label) }
let!(:assigned_merge_request) do
create(:merge_request,
assignees: [current_user],
source_project: project,
- author: create(:user))
+ author: author_user)
+ end
+
+ let!(:review_requested_merge_request) do
+ create(:merge_request,
+ reviewers: [current_user],
+ source_branch: 'review',
+ source_project: project,
+ author: author_user)
end
let!(:assigned_merge_request_from_fork) do
create(:merge_request,
source_branch: 'markdown', assignees: [current_user],
target_project: public_project, source_project: forked_project,
- author: create(:user))
+ author: author_user)
end
let!(:authored_merge_request) do
@@ -94,7 +103,7 @@ RSpec.describe 'Dashboard Merge Requests' do
create(:merge_request,
source_branch: 'fix',
source_project: project,
- author: create(:user))
+ author: author_user)
end
before do
@@ -111,6 +120,10 @@ RSpec.describe 'Dashboard Merge Requests' do
expect(page).not_to have_content(labeled_merge_request.title)
end
+ it 'does not show review requested merge requests' do
+ expect(page).not_to have_content(review_requested_merge_request.title)
+ end
+
it 'shows authored merge requests', :js do
reset_filters
input_filtered_search("author:=#{current_user.to_reference}")
@@ -159,4 +172,25 @@ RSpec.describe 'Dashboard Merge Requests' do
expect(find('.issues-filters')).to have_content('Created date')
end
end
+
+ context 'merge request review', :js do
+ let_it_be(:author_user) { create(:user) }
+ let!(:review_requested_merge_request) do
+ create(:merge_request,
+ reviewers: [current_user],
+ source_branch: 'review',
+ source_project: project,
+ author: author_user)
+ end
+
+ before do
+ visit merge_requests_dashboard_path(reviewer_username: current_user.username)
+ end
+
+ it 'displays review requested merge requests' do
+ expect(page).to have_content(review_requested_merge_request.title)
+
+ expect_tokens([reviewer_token(current_user.name)])
+ end
+ end
end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index 58352518d43..b2fda28f0ec 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe 'Dashboard shortcuts', :js do
visit root_dashboard_path
end
- it 'Navigate to tabs' do
+ it 'navigate to tabs' do
find('body').send_keys([:shift, 'I'])
check_page_title('Issues')
@@ -45,7 +45,7 @@ RSpec.describe 'Dashboard shortcuts', :js do
visit explore_root_path
end
- it 'Navigate to tabs' do
+ it 'navigate to tabs' do
find('body').send_keys([:shift, 'G'])
find('.nothing-here-block')
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
index 832f50932f4..9fa77d5917d 100644
--- a/spec/features/dashboard/user_filters_projects_spec.rb
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -173,11 +173,11 @@ RSpec.describe 'Dashboard > User filters projects' do
end
end
- it 'defaults to "Last updated"', :js do
+ it 'defaults to "Name"', :js do
page.find('.filtered-search-block #filtered-search-sorting-dropdown').click
active_sorting_option = page.first('.filtered-search-block #filtered-search-sorting-dropdown .is-active')
- expect(active_sorting_option).to have_content 'Last updated'
+ expect(active_sorting_option).to have_content 'Name'
end
context 'Sorting by name' do
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 0912df22924..55bdf4c244e 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -10,7 +10,9 @@ RSpec.describe 'Expand and collapse diffs', :js do
stub_feature_flags(increased_diff_limits: false)
allow(Gitlab::CurrentSettings).to receive(:diff_max_patch_bytes).and_return(100.kilobytes)
- sign_in(create(:admin))
+ admin = create(:admin)
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
# Ensure that undiffable.md is in .gitattributes
project.repository.copy_gitattributes(branch)
diff --git a/spec/features/file_uploads/git_lfs_spec.rb b/spec/features/file_uploads/git_lfs_spec.rb
index b902d7ab702..239afb1a1bb 100644
--- a/spec/features/file_uploads/git_lfs_spec.rb
+++ b/spec/features/file_uploads/git_lfs_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe 'Upload a git lfs object', :js do
include_context 'file upload requests helpers'
let_it_be(:project) { create(:project) }
- let_it_be(:user) { create(:user, :admin) }
+ let_it_be(:user) { project.owner }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }
@@ -19,7 +19,7 @@ RSpec.describe 'Upload a git lfs object', :js do
HTTParty.put(
url,
headers: headers,
- basic_auth: { user: user.username, password: personal_access_token.token },
+ basic_auth: { username: user.username, password: personal_access_token.token },
body: file.read
)
end
diff --git a/spec/features/file_uploads/multipart_invalid_uploads_spec.rb b/spec/features/file_uploads/multipart_invalid_uploads_spec.rb
index b3ace2e30ff..91c8e100e6a 100644
--- a/spec/features/file_uploads/multipart_invalid_uploads_spec.rb
+++ b/spec/features/file_uploads/multipart_invalid_uploads_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe 'Invalid uploads that must be rejected', :api, :js do
subject do
HTTParty.put(
url,
- basic_auth: { user: user.username, password: personal_access_token.token },
+ basic_auth: { username: user.username, password: personal_access_token.token },
body: body
)
end
diff --git a/spec/features/file_uploads/nuget_package_spec.rb b/spec/features/file_uploads/nuget_package_spec.rb
index fb1e0a54744..6e05e5d1a6e 100644
--- a/spec/features/file_uploads/nuget_package_spec.rb
+++ b/spec/features/file_uploads/nuget_package_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'Upload a nuget package', :api, :js do
subject do
HTTParty.put(
url,
- basic_auth: { user: user.username, password: personal_access_token.token },
+ basic_auth: { username: user.username, password: personal_access_token.token },
body: { package: file }
)
end
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index e6e4a55c1bb..19fb8e5f52c 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Global search' do
+ include AfterNextHelpers
+
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
@@ -22,9 +24,7 @@ RSpec.describe 'Global search' do
describe 'I search through the issues and I see pagination' do
before do
- allow_next_instance_of(SearchService) do |instance|
- allow(instance).to receive(:per_page).and_return(1)
- end
+ allow_next(SearchService).to receive(:per_page).and_return(1)
create_list(:issue, 2, project: project, title: 'initial')
end
diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb
index 29d0347086c..b25aa26d906 100644
--- a/spec/features/groups/board_spec.rb
+++ b/spec/features/groups/board_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'Group Boards' do
visit group_boards_path(group)
end
- it 'Adds an issue to the backlog' do
+ it 'adds an issue to the backlog' do
page.within(find('.board', match: :first)) do
issue_title = 'New Issue'
find(:css, '.issue-count-badge-add-button').click
diff --git a/spec/features/groups/container_registry_spec.rb b/spec/features/groups/container_registry_spec.rb
index 1b23b8b4bf9..cacabdda22d 100644
--- a/spec/features/groups/container_registry_spec.rb
+++ b/spec/features/groups/container_registry_spec.rb
@@ -51,13 +51,6 @@ RSpec.describe 'Container Registry', :js do
expect(page).to have_content 'my/image'
end
- it 'image repository delete is disabled' do
- visit_container_registry
-
- delete_btn = find('[title="Remove repository"]')
- expect(delete_btn).to be_disabled
- end
-
it 'navigates to repo details' do
visit_container_registry_details('my/image')
diff --git a/spec/features/groups/dependency_proxy_spec.rb b/spec/features/groups/dependency_proxy_spec.rb
index 9bbfdc488fb..51371ddc532 100644
--- a/spec/features/groups/dependency_proxy_spec.rb
+++ b/spec/features/groups/dependency_proxy_spec.rb
@@ -79,13 +79,19 @@ RSpec.describe 'Group Dependency Proxy' do
sign_in(developer)
end
- context 'group is private' do
- let(:group) { create(:group, :private) }
+ context 'feature flag is disabled' do
+ before do
+ stub_feature_flags(dependency_proxy_for_private_groups: false)
+ end
- it 'informs user that feature is only available for public groups' do
- visit path
+ context 'group is private' do
+ let(:group) { create(:group, :private) }
- expect(page).to have_content('Dependency proxy feature is limited to public groups for now.')
+ it 'informs user that feature is only available for public groups' do
+ visit path
+
+ expect(page).to have_content('Dependency proxy feature is limited to public groups for now.')
+ end
end
end
diff --git a/spec/features/groups/import_export/connect_instance_spec.rb b/spec/features/groups/import_export/connect_instance_spec.rb
new file mode 100644
index 00000000000..c0f967fd0b9
--- /dev/null
+++ b/spec/features/groups/import_export/connect_instance_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Import/Export - Connect to another instance', :js do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+
+ before_all do
+ group.add_owner(user)
+ end
+
+ before do
+ gitlab_sign_in(user)
+
+ visit new_group_path
+
+ find('#import-group-tab').click
+ end
+
+ context 'when the user provides valid credentials' do
+ it 'successfully connects to remote instance' do
+ source_url = 'https://gitlab.com'
+ pat = 'demo-pat'
+ stub_path = 'stub-group'
+
+ stub_request(:get, "%{url}/api/v4/groups?page=1&per_page=30&top_level_only=true" % { url: source_url }).to_return(
+ body: [{
+ id: 2595438,
+ web_url: 'https://gitlab.com/groups/auto-breakfast',
+ name: 'Stub',
+ path: stub_path,
+ full_name: 'Stub',
+ full_path: stub_path
+ }].to_json,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+
+ expect(page).to have_content 'Import groups from another instance of GitLab'
+
+ fill_in :bulk_import_gitlab_url, with: source_url
+ fill_in :bulk_import_gitlab_access_token, with: pat
+
+ click_on 'Connect instance'
+
+ expect(page).to have_content 'Importing groups from %{url}' % { url: source_url }
+ expect(page).to have_content stub_path
+ end
+ end
+
+ context 'when the user provides invalid url' do
+ it 'reports an error' do
+ source_url = 'invalid-url'
+ pat = 'demo-pat'
+
+ fill_in :bulk_import_gitlab_url, with: source_url
+ fill_in :bulk_import_gitlab_access_token, with: pat
+
+ click_on 'Connect instance'
+
+ expect(page).to have_content 'Specified URL cannot be used'
+ end
+ end
+
+ context 'when the user does not fill in source URL' do
+ it 'reports an error' do
+ pat = 'demo-pat'
+
+ fill_in :bulk_import_gitlab_access_token, with: pat
+
+ click_on 'Connect instance'
+
+ expect(page).to have_content 'Please fill in GitLab source URL'
+ end
+ end
+
+ context 'when the user does not fill in access token' do
+ it 'reports an error' do
+ source_url = 'https://gitlab.com'
+
+ fill_in :bulk_import_gitlab_url, with: source_url
+
+ click_on 'Connect instance'
+
+ expect(page).to have_content 'Please fill in your personal access token'
+ end
+ end
+end
diff --git a/spec/features/groups/import_export/import_file_spec.rb b/spec/features/groups/import_export/import_file_spec.rb
index f117b5d56e9..7018f3b1086 100644
--- a/spec/features/groups/import_export/import_file_spec.rb
+++ b/spec/features/groups/import_export/import_file_spec.rb
@@ -32,12 +32,12 @@ RSpec.describe 'Import/Export - Group Import', :js do
fill_in :group_name, with: group_name
find('#import-group-tab').click
- expect(page).to have_content 'Import a GitLab group export file'
+ expect(page).to have_content 'Import group from file'
attach_file(file) do
find('.js-filepicker-button').click
end
- expect { click_on 'Import group' }.to change { Group.count }.by 1
+ expect { click_on 'Import' }.to change { Group.count }.by 1
group = Group.find_by(name: group_name)
@@ -60,7 +60,7 @@ RSpec.describe 'Import/Export - Group Import', :js do
find('.js-filepicker-button').click
end
- expect { click_on 'Import group' }.to change { Group.count }.by 1
+ expect { click_on 'Import' }.to change { Group.count }.by 1
group = Group.find_by(name: 'Test Group Import')
expect(group.path).to eq 'custom-path'
@@ -94,7 +94,7 @@ RSpec.describe 'Import/Export - Group Import', :js do
find('.js-filepicker-button').click
end
- expect { click_on 'Import group' }.not_to change { Group.count }
+ expect { click_on 'Import' }.not_to change { Group.count }
page.within('.flash-container') do
expect(page).to have_content('Unable to process group import file')
diff --git a/spec/features/groups/members/filter_members_spec.rb b/spec/features/groups/members/filter_members_spec.rb
index b6d33b3f4aa..917b35659a6 100644
--- a/spec/features/groups/members/filter_members_spec.rb
+++ b/spec/features/groups/members/filter_members_spec.rb
@@ -11,8 +11,7 @@ RSpec.describe 'Groups > Members > Filter members', :js do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
- two_factor_auth_dropdown_toggle_selector = '[data-testid="member-filter-2fa-dropdown"] [data-testid="dropdown-toggle"]'
- active_inherited_members_filter_selector = '[data-testid="filter-members-with-inherited-permissions"] a.is-active'
+ filtered_search_bar_selector = '[data-testid="members-filtered-search-bar"]'
before do
group.add_owner(user)
@@ -27,7 +26,6 @@ RSpec.describe 'Groups > Members > Filter members', :js do
expect(member(0)).to include(user.name)
expect(member(1)).to include(user_with_2fa.name)
- expect(page).to have_css(two_factor_auth_dropdown_toggle_selector, text: 'Everyone')
end
it 'shows only 2FA members' do
@@ -35,7 +33,10 @@ RSpec.describe 'Groups > Members > Filter members', :js do
expect(member(0)).to include(user_with_2fa.name)
expect(all_rows.size).to eq(1)
- expect(page).to have_css(two_factor_auth_dropdown_toggle_selector, text: 'Enabled')
+
+ within filtered_search_bar_selector do
+ expect(page).to have_content '2FA = Enabled'
+ end
end
it 'shows only non 2FA members' do
@@ -43,7 +44,10 @@ RSpec.describe 'Groups > Members > Filter members', :js do
expect(member(0)).to include(user.name)
expect(all_rows.size).to eq(1)
- expect(page).to have_css(two_factor_auth_dropdown_toggle_selector, text: 'Disabled')
+
+ within filtered_search_bar_selector do
+ expect(page).to have_content '2FA = Disabled'
+ end
end
it 'shows inherited members by default' do
@@ -53,15 +57,16 @@ RSpec.describe 'Groups > Members > Filter members', :js do
expect(member(1)).to include(user_with_2fa.name)
expect(member(2)).to include(nested_group_user.name)
expect(all_rows.size).to eq(3)
-
- expect(page).to have_css(active_inherited_members_filter_selector, text: 'Show all members', visible: false)
end
it 'shows only group members' do
visit_members_list(nested_group, with_inherited_permissions: 'exclude')
expect(member(0)).to include(nested_group_user.name)
expect(all_rows.size).to eq(1)
- expect(page).to have_css(active_inherited_members_filter_selector, text: 'Show only direct members', visible: false)
+
+ within filtered_search_bar_selector do
+ expect(page).to have_content 'Membership = Direct'
+ end
end
it 'shows only inherited members' do
@@ -69,7 +74,10 @@ RSpec.describe 'Groups > Members > Filter members', :js do
expect(member(0)).to include(user.name)
expect(member(1)).to include(user_with_2fa.name)
expect(all_rows.size).to eq(2)
- expect(page).to have_css(active_inherited_members_filter_selector, text: 'Show only inherited members', visible: false)
+
+ within filtered_search_bar_selector do
+ expect(page).to have_content 'Membership = Inherited'
+ end
end
def visit_members_list(group, options = {})
diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb
index 0b2d2fd478d..fe5fed307d7 100644
--- a/spec/features/groups/members/search_members_spec.rb
+++ b/spec/features/groups/members/search_members_spec.rb
@@ -21,9 +21,10 @@ RSpec.describe 'Search group member', :js do
end
it 'renders member users' do
- page.within '[data-testid="user-search-form"]' do
- fill_in 'search', with: member.name
- find('[data-testid="user-search-submit"]').click
+ page.within '[data-testid="members-filtered-search-bar"]' do
+ find_field('Filter members').click
+ find('input').native.send_keys(member.name)
+ click_button 'Search'
end
expect(members_table).to have_content(member.name)
diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb
index f03cc36df18..68a748aa76a 100644
--- a/spec/features/groups/members/sort_members_spec.rb
+++ b/spec/features/groups/members/sort_members_spec.rb
@@ -9,8 +9,6 @@ RSpec.describe 'Groups > Members > Sort members', :js do
let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) }
let(:group) { create(:group) }
- dropdown_toggle_selector = '[data-testid="user-sort-dropdown"] [data-testid="dropdown-toggle"]'
-
before do
create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago)
create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago)
@@ -18,76 +16,174 @@ RSpec.describe 'Groups > Members > Sort members', :js do
sign_in(owner)
end
- it 'sorts alphabetically by default' do
- visit_members_list(sort: nil)
+ context 'when `group_members_filtered_search` feature flag is enabled' do
+ def expect_sort_by(text, sort_direction)
+ within('[data-testid="members-sort-dropdown"]') do
+ expect(page).to have_css('button[aria-haspopup="true"]', text: text)
+ expect(page).to have_button("Sorting Direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}")
+ end
+ end
- expect(first_row.text).to include(owner.name)
- expect(second_row.text).to include(developer.name)
- expect(page).to have_css(dropdown_toggle_selector, text: 'Name, ascending')
- end
+ it 'sorts by account by default' do
+ visit_members_list(sort: nil)
- it 'sorts by access level ascending' do
- visit_members_list(sort: :access_level_asc)
+ expect(first_row.text).to include(owner.name)
+ expect(second_row.text).to include(developer.name)
- expect(first_row.text).to include(developer.name)
- expect(second_row.text).to include(owner.name)
- expect(page).to have_css(dropdown_toggle_selector, text: 'Access level, ascending')
- end
+ expect_sort_by('Account', :asc)
+ end
- it 'sorts by access level descending' do
- visit_members_list(sort: :access_level_desc)
+ it 'sorts by max role ascending' do
+ visit_members_list(sort: :access_level_asc)
- expect(first_row.text).to include(owner.name)
- expect(second_row.text).to include(developer.name)
- expect(page).to have_css(dropdown_toggle_selector, text: 'Access level, descending')
- end
+ expect(first_row.text).to include(developer.name)
+ expect(second_row.text).to include(owner.name)
- it 'sorts by last joined' do
- visit_members_list(sort: :last_joined)
+ expect_sort_by('Max role', :asc)
+ end
- expect(first_row.text).to include(developer.name)
- expect(second_row.text).to include(owner.name)
- expect(page).to have_css(dropdown_toggle_selector, text: 'Last joined')
- end
+ it 'sorts by max role descending' do
+ visit_members_list(sort: :access_level_desc)
- it 'sorts by oldest joined' do
- visit_members_list(sort: :oldest_joined)
+ expect(first_row.text).to include(owner.name)
+ expect(second_row.text).to include(developer.name)
- expect(first_row.text).to include(owner.name)
- expect(second_row.text).to include(developer.name)
- expect(page).to have_css(dropdown_toggle_selector, text: 'Oldest joined')
- end
+ expect_sort_by('Max role', :desc)
+ end
- it 'sorts by name ascending' do
- visit_members_list(sort: :name_asc)
+ it 'sorts by access granted ascending' do
+ visit_members_list(sort: :last_joined)
- expect(first_row.text).to include(owner.name)
- expect(second_row.text).to include(developer.name)
- expect(page).to have_css(dropdown_toggle_selector, text: 'Name, ascending')
- end
+ expect(first_row.text).to include(developer.name)
+ expect(second_row.text).to include(owner.name)
- it 'sorts by name descending' do
- visit_members_list(sort: :name_desc)
+ expect_sort_by('Access granted', :asc)
+ end
- expect(first_row.text).to include(developer.name)
- expect(second_row.text).to include(owner.name)
- expect(page).to have_css(dropdown_toggle_selector, text: 'Name, descending')
- end
+ it 'sorts by access granted descending' do
+ visit_members_list(sort: :oldest_joined)
+
+ expect(first_row.text).to include(owner.name)
+ expect(second_row.text).to include(developer.name)
+
+ expect_sort_by('Access granted', :desc)
+ end
+
+ it 'sorts by account ascending' do
+ visit_members_list(sort: :name_asc)
+
+ expect(first_row.text).to include(owner.name)
+ expect(second_row.text).to include(developer.name)
+
+ expect_sort_by('Account', :asc)
+ end
+
+ it 'sorts by account descending' do
+ visit_members_list(sort: :name_desc)
+
+ expect(first_row.text).to include(developer.name)
+ expect(second_row.text).to include(owner.name)
+
+ expect_sort_by('Account', :desc)
+ end
+
+ it 'sorts by last sign-in ascending', :clean_gitlab_redis_shared_state do
+ visit_members_list(sort: :recent_sign_in)
+
+ expect(first_row.text).to include(owner.name)
+ expect(second_row.text).to include(developer.name)
+
+ expect_sort_by('Last sign-in', :asc)
+ end
- it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
- visit_members_list(sort: :recent_sign_in)
+ it 'sorts by last sign-in descending', :clean_gitlab_redis_shared_state do
+ visit_members_list(sort: :oldest_sign_in)
- expect(first_row.text).to include(owner.name)
- expect(second_row.text).to include(developer.name)
- expect(page).to have_css(dropdown_toggle_selector, text: 'Recent sign in')
+ expect(first_row.text).to include(developer.name)
+ expect(second_row.text).to include(owner.name)
+
+ expect_sort_by('Last sign-in', :desc)
+ end
end
- it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
- visit_members_list(sort: :oldest_sign_in)
+ context 'when `group_members_filtered_search` feature flag is disabled' do
+ dropdown_toggle_selector = '[data-testid="user-sort-dropdown"] [data-testid="dropdown-toggle"]'
+
+ before do
+ stub_feature_flags(group_members_filtered_search: false)
+ end
+
+ it 'sorts alphabetically by default' do
+ visit_members_list(sort: nil)
+
+ expect(first_row.text).to include(owner.name)
+ expect(second_row.text).to include(developer.name)
+ expect(page).to have_css(dropdown_toggle_selector, text: 'Name, ascending')
+ end
+
+ it 'sorts by access level ascending' do
+ visit_members_list(sort: :access_level_asc)
+
+ expect(first_row.text).to include(developer.name)
+ expect(second_row.text).to include(owner.name)
+ expect(page).to have_css(dropdown_toggle_selector, text: 'Access level, ascending')
+ end
+
+ it 'sorts by access level descending' do
+ visit_members_list(sort: :access_level_desc)
+
+ expect(first_row.text).to include(owner.name)
+ expect(second_row.text).to include(developer.name)
+ expect(page).to have_css(dropdown_toggle_selector, text: 'Access level, descending')
+ end
+
+ it 'sorts by last joined' do
+ visit_members_list(sort: :last_joined)
+
+ expect(first_row.text).to include(developer.name)
+ expect(second_row.text).to include(owner.name)
+ expect(page).to have_css(dropdown_toggle_selector, text: 'Last joined')
+ end
+
+ it 'sorts by oldest joined' do
+ visit_members_list(sort: :oldest_joined)
+
+ expect(first_row.text).to include(owner.name)
+ expect(second_row.text).to include(developer.name)
+ expect(page).to have_css(dropdown_toggle_selector, text: 'Oldest joined')
+ end
+
+ it 'sorts by name ascending' do
+ visit_members_list(sort: :name_asc)
+
+ expect(first_row.text).to include(owner.name)
+ expect(second_row.text).to include(developer.name)
+ expect(page).to have_css(dropdown_toggle_selector, text: 'Name, ascending')
+ end
+
+ it 'sorts by name descending' do
+ visit_members_list(sort: :name_desc)
+
+ expect(first_row.text).to include(developer.name)
+ expect(second_row.text).to include(owner.name)
+ expect(page).to have_css(dropdown_toggle_selector, text: 'Name, descending')
+ end
+
+ it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
+ visit_members_list(sort: :recent_sign_in)
+
+ expect(first_row.text).to include(owner.name)
+ expect(second_row.text).to include(developer.name)
+ expect(page).to have_css(dropdown_toggle_selector, text: 'Recent sign in')
+ end
+
+ it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
+ visit_members_list(sort: :oldest_sign_in)
- expect(first_row.text).to include(developer.name)
- expect(second_row.text).to include(owner.name)
- expect(page).to have_css(dropdown_toggle_selector, text: 'Oldest sign in')
+ expect(first_row.text).to include(developer.name)
+ expect(second_row.text).to include(owner.name)
+ expect(page).to have_css(dropdown_toggle_selector, text: 'Oldest sign in')
+ end
end
def visit_members_list(sort:)
diff --git a/spec/features/groups/members/tabs_spec.rb b/spec/features/groups/members/tabs_spec.rb
index fa77d1a2ff8..2f95e9fa6d3 100644
--- a/spec/features/groups/members/tabs_spec.rb
+++ b/spec/features/groups/members/tabs_spec.rb
@@ -62,9 +62,10 @@ RSpec.describe 'Groups > Members > Tabs' do
click_link 'Invited'
- page.within '[data-testid="user-search-form"]' do
- fill_in 'search_invited', with: 'email'
- find('button[type="submit"]').click
+ page.within '[data-testid="members-filtered-search-bar"]' do
+ find_field('Search invited').click
+ find('input').native.send_keys('email')
+ click_button 'Search'
end
end
@@ -74,9 +75,10 @@ RSpec.describe 'Groups > Members > Tabs' do
before do
click_link 'Members'
- page.within '[data-testid="user-search-form"]' do
- fill_in 'search', with: 'test'
- find('button[type="submit"]').click
+ page.within '[data-testid="members-filtered-search-bar"]' do
+ find_field('Filter members').click
+ find('input').native.send_keys('test')
+ click_button 'Search'
end
end
diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb
index dec07eb3783..a4c450c9a2c 100644
--- a/spec/features/groups/navbar_spec.rb
+++ b/spec/features/groups/navbar_spec.rb
@@ -33,6 +33,7 @@ RSpec.describe 'Group navbar' do
nav_item: _('Merge Requests'),
nav_sub_items: []
},
+ (security_and_compliance_nav_item if Gitlab.ee?),
(push_rules_nav_item if Gitlab.ee?),
{
nav_item: _('Kubernetes'),
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 97732374eb9..3a42fd508b4 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -193,4 +193,69 @@ RSpec.describe 'Group show page' do
it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet'
end
+
+ context 'structured schema markup' do
+ let_it_be(:group) { create(:group, :public, :with_avatar, description: 'foo') }
+ let_it_be(:subgroup) { create(:group, :public, :with_avatar, parent: group, description: 'bar') }
+ let_it_be_with_reload(:project) { create(:project, :public, :with_avatar, namespace: group, description: 'foo') }
+ let_it_be(:subproject) { create(:project, :public, :with_avatar, namespace: subgroup, description: 'bar') }
+
+ it 'shows Organization structured markup', :js do
+ visit path
+ wait_for_all_requests
+
+ aggregate_failures do
+ expect(page).to have_selector('.content[itemscope][itemtype="https://schema.org/Organization"]')
+
+ page.within('.group-home-panel') do
+ expect(page).to have_selector('img.avatar[itemprop="logo"]')
+ expect(page).to have_selector('[itemprop="name"]', text: group.name)
+ expect(page).to have_selector('[itemprop="description"]', text: group.description)
+ end
+
+ page.within('[itemprop="owns"][itemtype="https://schema.org/SoftwareSourceCode"]') do
+ expect(page).to have_selector('img.avatar[itemprop="image"]')
+ expect(page).to have_selector('[itemprop="name"]', text: project.name)
+ expect(page).to have_selector('[itemprop="description"]', text: project.description)
+ end
+
+ # Finding the subgroup row and expanding it
+ el = find('[itemprop="subOrganization"][itemtype="https://schema.org/Organization"]')
+ el.click
+ wait_for_all_requests
+ page.within(el) do
+ expect(page).to have_selector('img.avatar[itemprop="logo"]')
+ expect(page).to have_selector('[itemprop="name"]', text: subgroup.name)
+ expect(page).to have_selector('[itemprop="description"]', text: subgroup.description)
+
+ page.within('[itemprop="owns"][itemtype="https://schema.org/SoftwareSourceCode"]') do
+ expect(page).to have_selector('img.avatar[itemprop="image"]')
+ expect(page).to have_selector('[itemprop="name"]', text: subproject.name)
+ expect(page).to have_selector('[itemprop="description"]', text: subproject.description)
+ end
+ end
+ end
+ end
+
+ it 'does not include structured markup in shared projects tab', :js do
+ other_project = create(:project, :public)
+ other_project.project_group_links.create!(group: group)
+
+ visit group_shared_path(group)
+ wait_for_all_requests
+
+ expect(page).to have_selector('li.group-row')
+ expect(page).not_to have_selector('[itemprop="owns"][itemtype="https://schema.org/SoftwareSourceCode"]')
+ end
+
+ it 'does not include structured markup in archived projects tab', :js do
+ project.update!(archived: true)
+
+ visit group_archived_path(group)
+ wait_for_all_requests
+
+ expect(page).to have_selector('li.group-row')
+ expect(page).not_to have_selector('[itemprop="owns"][itemtype="https://schema.org/SoftwareSourceCode"]')
+ end
+ end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index b9fd3a1a5cc..c9a0844932a 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'Group' do
- let(:user) { create(:admin) }
+ let_it_be(:user) { create(:user) }
before do
sign_in(user)
@@ -21,8 +21,6 @@ RSpec.describe 'Group' do
end
describe 'as a non-admin' do
- let(:user) { create(:user) }
-
it 'creates a group and persists visibility radio selection', :js do
stub_application_setting(default_group_visibility: :private)
@@ -38,6 +36,15 @@ RSpec.describe 'Group' do
end
end
+ describe 'with expected fields' do
+ it 'renders from as expected', :aggregate_failures do
+ expect(page).to have_field('name')
+ expect(page).to have_field('group_path')
+ expect(page).to have_field('group_visibility_level_0')
+ expect(page).not_to have_field('description')
+ end
+ end
+
describe 'with space in group path' do
it 'renders new group form with validation errors' do
fill_in 'Group URL', with: 'space group'
@@ -137,9 +144,11 @@ RSpec.describe 'Group' do
end
describe 'create a nested group', :js do
- let(:group) { create(:group, path: 'foo') }
+ let_it_be(:group) { create(:group, path: 'foo') }
context 'as admin' do
+ let(:user) { create(:admin) }
+
before do
visit new_group_path(group, parent_id: group.id)
end
@@ -185,11 +194,13 @@ RSpec.describe 'Group' do
end
describe 'group edit', :js do
- let(:group) { create(:group, :public) }
- let(:path) { edit_group_path(group) }
+ let_it_be(:group) { create(:group, :public) }
+ let(:path) { edit_group_path(group) }
let(:new_name) { 'new-name' }
before do
+ group.add_owner(user)
+
visit path
end
@@ -200,6 +211,8 @@ RSpec.describe 'Group' do
it 'saves new settings' do
page.within('.gs-general') do
+ # Have to reset it to '' so it overwrites rather than appends
+ fill_in('group_name', with: '')
fill_in 'group_name', with: new_name
click_button 'Save changes'
end
@@ -226,8 +239,12 @@ RSpec.describe 'Group' do
end
describe 'group page with markdown description' do
- let(:group) { create(:group) }
- let(:path) { group_path(group) }
+ let_it_be(:group) { create(:group) }
+ let(:path) { group_path(group) }
+
+ before do
+ group.add_owner(user)
+ end
it 'parses Markdown' do
group.update_attribute(:description, 'This is **my** group')
@@ -263,9 +280,13 @@ RSpec.describe 'Group' do
end
describe 'group page with nested groups', :js do
- let!(:group) { create(:group) }
- let!(:nested_group) { create(:group, parent: group) }
- let!(:project) { create(:project, namespace: group) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:nested_group) { create(:group, parent: group) }
+ let_it_be(:project) { create(:project, namespace: group) }
+
+ before do
+ group.add_owner(user)
+ end
it 'renders projects and groups on the page' do
visit group_path(group)
@@ -292,7 +313,15 @@ RSpec.describe 'Group' do
end
describe 'new subgroup / project button' do
- let(:group) { create(:group, project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS, subgroup_creation_level: Gitlab::Access::OWNER_SUBGROUP_ACCESS) }
+ let_it_be(:group, reload: true) do
+ create(:group,
+ project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS,
+ subgroup_creation_level: Gitlab::Access::OWNER_SUBGROUP_ACCESS)
+ end
+
+ before do
+ group.add_owner(user)
+ end
context 'when user has subgroup creation permissions but not project creation permissions' do
it 'only displays "New subgroup" button' do
@@ -325,6 +354,7 @@ RSpec.describe 'Group' do
context 'when user has project and subgroup creation permissions' do
it 'displays "New subgroup" and "New project" buttons' do
group.update!(project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
+
visit group_path(group)
page.within '[data-testid="group-buttons"]' do
diff --git a/spec/features/ide/user_sees_editor_info_spec.rb b/spec/features/ide/user_sees_editor_info_spec.rb
deleted file mode 100644
index 3760d6bd435..00000000000
--- a/spec/features/ide/user_sees_editor_info_spec.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'IDE user sees editor info', :js do
- include WebIdeSpecHelpers
-
- let_it_be(:project) { create(:project, :public, :repository) }
- let_it_be(:user) { project.owner }
-
- before do
- sign_in(user)
-
- ide_visit(project)
- end
-
- it 'shows line position' do
- ide_open_file('README.md')
-
- within find('.ide-status-bar') do
- expect(page).to have_content('1:1')
- end
-
- ide_set_editor_position(4, 10)
-
- within find('.ide-status-bar') do
- expect(page).not_to have_content('1:1')
- expect(page).to have_content('4:10')
- end
- end
-
- it 'updates after rename' do
- ide_open_file('README.md')
- ide_set_editor_position(4, 10)
-
- within find('.ide-status-bar') do
- expect(page).to have_content('markdown')
- expect(page).to have_content('4:10')
- end
-
- ide_rename_file('README.md', 'READMEZ.txt')
-
- within find('.ide-status-bar') do
- expect(page).to have_content('plaintext')
- expect(page).to have_content('1:1')
- end
- end
-
- it 'persists position after rename' do
- ide_open_file('README.md')
- ide_set_editor_position(4, 10)
-
- ide_open_file('files/js/application.js')
- ide_rename_file('README.md', 'READING_RAINBOW.md')
-
- ide_open_file('READING_RAINBOW.md')
-
- within find('.ide-status-bar') do
- expect(page).to have_content('4:10')
- end
- end
-
- it 'persists position' do
- ide_open_file('README.md')
- ide_set_editor_position(4, 10)
-
- ide_close_file('README.md')
- ide_open_file('README.md')
-
- within find('.ide-status-bar') do
- expect(page).to have_content('markdown')
- expect(page).to have_content('4:10')
- end
- end
-
- it 'persists viewer' do
- ide_open_file('README.md')
- click_link('Preview Markdown')
-
- within find('.md-previewer') do
- expect(page).to have_content('testme')
- end
-
- # Switch away from and back to the file
- ide_open_file('.gitignore')
- ide_open_file('README.md')
-
- # Preview is still enabled
- within find('.md-previewer') do
- expect(page).to have_content('testme')
- end
- end
-end
diff --git a/spec/features/incidents/user_views_incident_spec.rb b/spec/features/incidents/user_views_incident_spec.rb
index 3595f5c03ec..b94ce3cd06f 100644
--- a/spec/features/incidents/user_views_incident_spec.rb
+++ b/spec/features/incidents/user_views_incident_spec.rb
@@ -13,8 +13,6 @@ RSpec.describe "User views incident" do
end
before do
- stub_feature_flags(vue_issue_header: false)
-
sign_in(user)
visit(project_issues_incident_path(project, incident))
@@ -24,10 +22,12 @@ RSpec.describe "User views incident" do
it_behaves_like 'page meta description', ' Description header Lorem ipsum dolor sit amet'
- it 'shows the merge request and incident actions', :aggregate_failures do
- expect(page).to have_link('New incident')
+ it 'shows the merge request and incident actions', :js, :aggregate_failures do
+ click_button 'Incident actions'
+
+ expect(page).to have_link('New incident', href: new_project_issue_path(project, { issuable_template: 'incident', issue: { issue_type: 'incident' } }))
expect(page).to have_button('Create merge request')
- expect(page).to have_link('Close incident')
+ expect(page).to have_button('Close incident')
end
context 'when the project is archived' do
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 0f0146a26a2..d773126e00c 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
@@ -105,7 +105,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j
visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
- it 'Shows a notice to ask someone else to resolve the threads' do
+ it 'shows a notice to ask someone else to resolve the threads' do
expect(page).to have_content("The threads at #{merge_request.to_reference} will stay unresolved. Ask someone with permission to resolve them.")
end
end
diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
index b449939a70c..99dc71f0559 100644
--- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
@@ -31,7 +31,8 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue',
visit project_merge_request_path(project, merge_request)
end
- it 'does not show a link to create a new issue' do
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/285453
+ xit 'does not show a link to create a new issue' do
expect(page).not_to have_css resolve_discussion_selector
end
end
@@ -81,7 +82,7 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue',
discussion_to_resolve: discussion.id)
end
- it 'Shows a notice to ask someone else to resolve the threads' do
+ it 'shows a notice to ask someone else to resolve the threads' do
expect(page).to have_content("The thread at #{merge_request.to_reference}"\
" (discussion #{discussion.first_note.id}) will stay unresolved."\
" Ask someone with permission to resolve it.")
diff --git a/spec/features/issuables/discussion_lock_spec.rb b/spec/features/issues/discussion_lock_spec.rb
index 13f1742fbf6..13f1742fbf6 100644
--- a/spec/features/issuables/discussion_lock_spec.rb
+++ b/spec/features/issues/discussion_lock_spec.rb
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 080943da185..4f4584e7dce 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -153,6 +153,14 @@ RSpec.describe 'Filter issues', :js do
end
end
+ describe 'filter by reviewer' do
+ it 'does not allow filtering by reviewer' do
+ find('.filtered-search').click
+
+ expect(page).not_to have_button('Reviewer')
+ end
+ end
+
describe 'filter issues by label' do
context 'only label' do
it 'filters issues by searched label' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 06f79f94e8d..07bf821a590 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -418,6 +418,46 @@ RSpec.describe 'GFM autocomplete', :js do
end
end
+ context 'when other notes are destroyed' do
+ let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
+
+ # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729
+ it 'keeps autocomplete key listeners' do
+ visit project_issue_path(project, issue)
+ note = find('#note-body')
+
+ start_comment_with_emoji(note)
+
+ start_and_cancel_discussion
+
+ note.fill_in(with: '')
+ start_comment_with_emoji(note)
+ note.native.send_keys(:enter)
+
+ expect(note.value).to eql('Hello :100: ')
+ end
+
+ def start_comment_with_emoji(note)
+ note.native.send_keys('Hello :10')
+
+ wait_for_requests
+
+ find('.atwho-view li', text: '100')
+ end
+
+ def start_and_cancel_discussion
+ click_button('Reply...')
+
+ fill_in('note_note', with: 'Whoops!')
+
+ page.accept_alert 'Are you sure you want to cancel creating this comment?' do
+ click_button('Cancel')
+ end
+
+ wait_for_requests
+ end
+ end
+
shared_examples 'autocomplete suggestions' do
it 'suggests objects correctly' do
page.within '.timeline-content-form' do
@@ -550,6 +590,15 @@ RSpec.describe 'GFM autocomplete', :js do
expect(find('.tribute-container ul', visible: true)).to have_text('alert milestone')
end
+ it 'does not open autocomplete menu when trigger character is prefixed with text' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('testing')
+ find('#note-body').native.send_keys('@')
+ end
+
+ expect(page).not_to have_selector('.tribute-container', visible: true)
+ end
+
it 'selects the first item for assignee dropdowns' do
page.within '.timeline-content-form' do
find('#note-body').native.send_keys('@')
@@ -618,21 +667,6 @@ RSpec.describe 'GFM autocomplete', :js do
expect(page).to have_selector('.tribute-container', visible: true)
end
- it "does not show dropdown when preceded with a special character" do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys("@")
- end
-
- expect(page).to have_selector('.tribute-container', visible: true)
-
- page.within '.timeline-content-form' do
- note.native.send_keys("@")
- end
-
- expect(page).not_to have_selector('.tribute-container')
- end
-
it "does not throw an error if no labels exist" do
note = find('#note-body')
page.within '.timeline-content-form' do
@@ -653,14 +687,6 @@ RSpec.describe 'GFM autocomplete', :js do
expect_to_wrap(false, user_item, note, user.username)
end
- it 'doesn\'t open autocomplete after non-word character' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys("@#{user.username[0..2]}!")
- end
-
- expect(page).not_to have_selector('.tribute-container')
- end
-
it 'triggers autocomplete after selecting a quick action' do
note = find('#note-body')
page.within '.timeline-content-form' do
@@ -848,46 +874,6 @@ RSpec.describe 'GFM autocomplete', :js do
it_behaves_like 'autocomplete suggestions'
end
-
- context 'when other notes are destroyed' do
- let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
-
- # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729
- it 'keeps autocomplete key listeners' do
- visit project_issue_path(project, issue)
- note = find('#note-body')
-
- start_comment_with_emoji(note)
-
- start_and_cancel_discussion
-
- note.fill_in(with: '')
- start_comment_with_emoji(note)
- note.native.send_keys(:enter)
-
- expect(note.value).to eql('Hello :100: ')
- end
-
- def start_comment_with_emoji(note)
- note.native.send_keys('Hello :10')
-
- wait_for_requests
-
- find('.atwho-view li', text: '100')
- end
-
- def start_and_cancel_discussion
- click_button('Reply...')
-
- fill_in('note_note', with: 'Whoops!')
-
- page.accept_alert 'Are you sure you want to cancel creating this comment?' do
- click_button('Cancel')
- end
-
- wait_for_requests
- end
- end
end
private
diff --git a/spec/features/issues/issue_header_spec.rb b/spec/features/issues/issue_header_spec.rb
new file mode 100644
index 00000000000..cf375d8fb67
--- /dev/null
+++ b/spec/features/issues/issue_header_spec.rb
@@ -0,0 +1,164 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'issue header', :js do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:closed_issue) { create(:issue, :closed, project: project) }
+ let_it_be(:closed_locked_issue) { create(:issue, :closed, :locked, project: project) }
+ let_it_be(:authored_issue) { create(:issue, project: project, author: user) }
+
+ context 'when user has permission to update' do
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ context 'within the issue actions dropdown menu' do
+ before do
+ visit project_issue_path(project, issue)
+
+ # Click on the ellipsis icon
+ click_button 'Issue actions'
+ end
+
+ it 'only shows the "New issue" and "Report abuse" items', :aggregate_failures do
+ expect(page).to have_link 'New issue'
+ expect(page).to have_link 'Report abuse'
+ expect(page).not_to have_link 'Submit as spam'
+ end
+ end
+
+ context 'when the issue is open' do
+ before do
+ visit project_issue_path(project, issue)
+ end
+
+ it 'has a "Close issue" button' do
+ expect(page).to have_button 'Close issue'
+ end
+ end
+
+ context 'when the issue is closed' do
+ before do
+ visit project_issue_path(project, closed_issue)
+ end
+
+ it 'has a "Reopen issue" button' do
+ expect(page).to have_button 'Reopen issue'
+ end
+ end
+
+ context 'when the issue is closed and locked' do
+ before do
+ visit project_issue_path(project, closed_locked_issue)
+ end
+
+ it 'does not have a "Reopen issue" button' do
+ expect(page).not_to have_button 'Reopen issue'
+ end
+ end
+
+ context 'when the current user is the issue author' do
+ before do
+ visit project_issue_path(project, authored_issue)
+ end
+
+ it 'does not show "Report abuse" link in dropdown' do
+ click_button 'Issue actions'
+
+ expect(page).not_to have_link 'Report abuse'
+ end
+ end
+ end
+
+ context 'when user is admin and the project is set up for spam' do
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:user_agent_detail) { create(:user_agent_detail, subject: issue) }
+
+ before do
+ stub_application_setting(akismet_enabled: true)
+ project.add_maintainer(admin)
+ sign_in(admin)
+ end
+
+ context 'within the issue actions dropdown menu' do
+ before do
+ visit project_issue_path(project, issue)
+
+ # Click on the ellipsis icon
+ click_button 'Issue actions'
+ end
+
+ it 'has "Submit as spam" item' do
+ expect(page).to have_link 'Submit as spam'
+ end
+ end
+ end
+
+ context 'when user does not have permission to update' do
+ before do
+ project.add_guest(user)
+ sign_in(user)
+ end
+
+ context 'within the issue actions dropdown menu' do
+ before do
+ visit project_issue_path(project, issue)
+
+ # Click on the ellipsis icon
+ click_button 'Issue actions'
+ end
+
+ it 'only shows the "New issue" and "Report abuse" items', :aggregate_failures do
+ expect(page).to have_link 'New issue'
+ expect(page).to have_link 'Report abuse'
+ expect(page).not_to have_link 'Submit as spam'
+ end
+ end
+
+ context 'when the issue is open' do
+ before do
+ visit project_issue_path(project, issue)
+ end
+
+ it 'does not have a "Close issue" button' do
+ expect(page).not_to have_button 'Close issue'
+ end
+ end
+
+ context 'when the issue is closed' do
+ before do
+ visit project_issue_path(project, closed_issue)
+ end
+
+ it 'does not have a "Reopen issue" button' do
+ expect(page).not_to have_button 'Reopen issue'
+ end
+ end
+
+ context 'when the issue is closed and locked' do
+ before do
+ visit project_issue_path(project, closed_locked_issue)
+ end
+
+ it 'does not have a "Reopen issue" button' do
+ expect(page).not_to have_button 'Reopen issue'
+ end
+ end
+
+ context 'when the current user is the issue author' do
+ before do
+ visit project_issue_path(project, authored_issue)
+ end
+
+ it 'does not show "Report abuse" link in dropdown' do
+ click_button 'Issue actions'
+
+ expect(page).not_to have_link 'Report abuse'
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/issue_state_spec.rb b/spec/features/issues/issue_state_spec.rb
new file mode 100644
index 00000000000..0ef6eb56dff
--- /dev/null
+++ b/spec/features/issues/issue_state_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'issue state', :js do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ shared_examples 'issue closed' do |selector|
+ it 'can close an issue' do
+ expect(find('.status-box')).to have_content 'Open'
+
+ within selector do
+ click_button 'Close issue'
+ end
+
+ expect(find('.status-box')).to have_content 'Closed'
+ end
+ end
+
+ shared_examples 'issue reopened' do |selector|
+ it 'can reopen an issue' do
+ expect(find('.status-box')).to have_content 'Closed'
+
+ within selector do
+ click_button 'Reopen issue'
+ end
+
+ expect(find('.status-box')).to have_content 'Open'
+ end
+ end
+
+ describe 'when open' do
+ context 'when clicking the top `Close issue` button', :aggregate_failures do
+ let(:open_issue) { create(:issue, project: project) }
+
+ before do
+ visit project_issue_path(project, open_issue)
+ end
+
+ it_behaves_like 'issue closed', '.detail-page-header'
+ end
+
+ context 'when clicking the bottom `Close issue` button', :aggregate_failures do
+ let(:open_issue) { create(:issue, project: project) }
+
+ before do
+ visit project_issue_path(project, open_issue)
+ end
+
+ it_behaves_like 'issue closed', '.timeline-content-form'
+ end
+ end
+
+ describe 'when closed' do
+ context 'when clicking the top `Reopen issue` button', :aggregate_failures do
+ let(:closed_issue) { create(:issue, project: project, state: 'closed') }
+
+ before do
+ visit project_issue_path(project, closed_issue)
+ end
+
+ it_behaves_like 'issue reopened', '.detail-page-header'
+ end
+
+ context 'when clicking the bottom `Reopen issue` button', :aggregate_failures do
+ let(:closed_issue) { create(:issue, project: project, state: 'closed') }
+
+ before do
+ visit project_issue_path(project, closed_issue)
+ end
+
+ it_behaves_like 'issue reopened', '.timeline-content-form'
+ end
+ end
+end
diff --git a/spec/features/issues/keyboard_shortcut_spec.rb b/spec/features/issues/keyboard_shortcut_spec.rb
index ab40f124257..502412bab5d 100644
--- a/spec/features/issues/keyboard_shortcut_spec.rb
+++ b/spec/features/issues/keyboard_shortcut_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'Issues shortcut', :js do
let(:project) { create(:project) }
before do
- sign_in(create(:admin))
+ sign_in(project.owner)
visit project_path(project)
end
@@ -23,7 +23,7 @@ RSpec.describe 'Issues shortcut', :js do
let(:project) { create(:project, :issues_disabled) }
before do
- sign_in(create(:admin))
+ sign_in(project.owner)
visit project_path(project)
end
diff --git a/spec/features/issuables/related_issues_spec.rb b/spec/features/issues/related_issues_spec.rb
index 837859bbe26..837859bbe26 100644
--- a/spec/features/issuables/related_issues_spec.rb
+++ b/spec/features/issues/related_issues_spec.rb
diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb
index 1512d539dec..02804d84a21 100644
--- a/spec/features/issues/service_desk_spec.rb
+++ b/spec/features/issues/service_desk_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
RSpec.describe 'Service Desk Issue Tracker', :js do
let(:project) { create(:project, :private, service_desk_enabled: true) }
- let(:user) { create(:user) }
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:support_bot) { User.support_bot }
before do
# The following two conditions equate to Gitlab::ServiceDesk.supported == true
@@ -27,6 +29,16 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
end
end
+ context 'issue page' do
+ let(:service_desk_issue) { create(:issue, project: project, author: support_bot, service_desk_reply_to: 'service.desk@example.com') }
+
+ it 'shows service_desk_reply_to in issue header' do
+ visit project_issue_path(project, service_desk_issue)
+
+ expect(page).to have_text('by service.desk@example.com via GitLab Support Bot')
+ end
+ end
+
describe 'issues list' do
context 'when service desk is supported' do
context 'when there are no issues' do
@@ -66,10 +78,10 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
end
context 'when there are issues' do
- let(:support_bot) { User.support_bot }
- let(:other_user) { create(:user) }
- let!(:service_desk_issue) { create(:issue, project: project, author: support_bot) }
- let!(:other_user_issue) { create(:issue, project: project, author: other_user) }
+ let_it_be(:project) { create(:project, :private, service_desk_enabled: true) }
+ let_it_be(:other_user) { create(:user) }
+ let_it_be(:service_desk_issue) { create(:issue, project: project, author: support_bot, service_desk_reply_to: 'service.desk@example.com') }
+ let_it_be(:other_user_issue) { create(:issue, project: project, author: other_user) }
describe 'service desk info content' do
before do
@@ -94,6 +106,10 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
it 'only displays issues created by support bot' do
expect(page).to have_selector('.issues-list .issue', count: 1)
end
+
+ it 'shows service_desk_reply_to in issues list' do
+ expect(page).to have_text('by service.desk@example.com via GitLab Support Bot')
+ end
end
describe 'search box' do
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index 11b905735de..9d4a6cdb522 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -22,6 +22,7 @@ RSpec.describe "Issues > User edits issue", :js do
context "from edit page" do
before do
+ stub_licensed_features(multiple_issue_assignees: false)
visit edit_project_issue_path(project, issue)
end
diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb
index c5eb3f415ff..d88b816b186 100644
--- a/spec/features/issues/user_uses_quick_actions_spec.rb
+++ b/spec/features/issues/user_uses_quick_actions_spec.rb
@@ -43,5 +43,6 @@ RSpec.describe 'Issues > User uses quick actions', :js do
it_behaves_like 'create_merge_request quick action'
it_behaves_like 'move quick action'
it_behaves_like 'zoom quick actions'
+ it_behaves_like 'clone quick action'
end
end
diff --git a/spec/features/issues/user_views_issue_spec.rb b/spec/features/issues/user_views_issue_spec.rb
index 4128f3478bb..8792d76981f 100644
--- a/spec/features/issues/user_views_issue_spec.rb
+++ b/spec/features/issues/user_views_issue_spec.rb
@@ -13,8 +13,6 @@ RSpec.describe "User views issue" do
end
before do
- stub_feature_flags(vue_issue_header: false)
-
sign_in(user)
visit(project_issue_path(project, issue))
@@ -24,10 +22,12 @@ RSpec.describe "User views issue" do
it_behaves_like 'page meta description', ' Description header Lorem ipsum dolor sit amet'
- it 'shows the merge request and issue actions', :aggregate_failures do
- expect(page).to have_link('New issue')
+ it 'shows the merge request and issue actions', :js, :aggregate_failures do
+ click_button 'Issue actions'
+
+ expect(page).to have_link('New issue', href: new_project_issue_path(project))
expect(page).to have_button('Create merge request')
- expect(page).to have_link('Close issue')
+ expect(page).to have_button('Close issue')
end
context 'when the project is archived' do
diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb
index fbf4e531db1..c9dc764f93b 100644
--- a/spec/features/markdown/copy_as_gfm_spec.rb
+++ b/spec/features/markdown/copy_as_gfm_spec.rb
@@ -7,10 +7,6 @@ RSpec.describe 'Copy as GFM', :js do
include RepoHelpers
include ActionView::Helpers::JavaScriptHelper
- before do
- sign_in(create(:admin))
- end
-
describe 'Copying rendered GFM' do
before do
@feat = MarkdownFeature.new
@@ -18,6 +14,9 @@ RSpec.describe 'Copy as GFM', :js do
# `markdown` helper expects a `@project` variable
@project = @feat.project
+ user = create(:user)
+ @project.add_maintainer(user)
+ sign_in(user)
visit project_issue_path(@project, @feat.issue)
end
@@ -650,6 +649,10 @@ RSpec.describe 'Copy as GFM', :js do
describe 'Copying code' do
let(:project) { create(:project, :repository) }
+ before do
+ sign_in(project.owner)
+ end
+
context 'from a diff' do
shared_examples 'copying code from a diff' do
context 'selecting one word of text' do
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index 58314a49c4b..23cdd9d2ce5 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -48,7 +48,7 @@ RSpec.describe 'Mermaid rendering', :js do
expect(page.html.scan(expected).count).to be(4)
end
- it 'renders only 2 Mermaid blocks and ', :js, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/234081' } do
+ it 'renders only 2 Mermaid blocks and', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/234081' do
description = <<~MERMAID
```mermaid
graph LR
@@ -78,7 +78,7 @@ RSpec.describe 'Mermaid rendering', :js do
end
end
- it 'correctly sizes mermaid diagram inside <details> block', :js do
+ it 'correctly sizes mermaid diagram inside <details> block' do
description = <<~MERMAID
<details>
<summary>Click to show diagram</summary>
@@ -112,7 +112,7 @@ RSpec.describe 'Mermaid rendering', :js do
end
end
- it 'correctly sizes mermaid diagram block', :js do
+ it 'correctly sizes mermaid diagram block' do
description = <<~MERMAID
```mermaid
graph TD;
@@ -134,7 +134,7 @@ RSpec.describe 'Mermaid rendering', :js do
expect(page).to have_css('svg.mermaid[style*="max-width"][width="100%"]')
end
- it 'display button when diagram exceeds length', :js do
+ it 'display button when diagram exceeds length', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/287806' do
graph_edges = "A-->B;B-->A;" * 420
description = <<~MERMAID
diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/merge_request/close_reopen_report_toggle_spec.rb
index 867d2ff7aae..8a4277d87c9 100644
--- a/spec/features/issuables/close_reopen_report_toggle_spec.rb
+++ b/spec/features/merge_request/close_reopen_report_toggle_spec.rb
@@ -7,51 +7,10 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
let(:user) { create(:user) }
- before do
- stub_feature_flags(vue_issue_header: false)
- end
-
- shared_examples 'an issuable close/reopen/report toggle' do
- let(:container) { find('.issuable-close-dropdown') }
- let(:human_model_name) { issuable.model_name.human.downcase }
-
- it 'shows toggle' do
- expect(page).to have_button("Close #{human_model_name}")
- expect(page).to have_selector('.issuable-close-dropdown')
- end
-
- it 'opens a dropdown when toggle is clicked' do
- container.find('.dropdown-toggle').click
-
- expect(container).to have_selector('.dropdown-menu')
- expect(container).to have_content("Close #{human_model_name}")
- expect(container).to have_content('Report abuse')
- expect(container).to have_content("Report #{human_model_name.pluralize} that are abusive, inappropriate or spam.")
-
- if issuable.is_a?(MergeRequest)
- page.within('.js-issuable-close-dropdown') do
- expect(page).to have_link('Close merge request')
- end
- else
- expect(container).to have_selector('.close-item.droplab-item-selected')
- end
-
- expect(container).to have_selector('.report-item')
- expect(container).not_to have_selector('.report-item.droplab-item-selected')
- expect(container).not_to have_selector('.reopen-item')
- end
-
- it 'links to Report Abuse' do
- container.find('.dropdown-toggle').click
- container.find('.report-abuse-link').click
-
- expect(page).to have_content('Report abuse to admin')
- end
- end
-
- context 'on an issue' do
- let(:project) { create(:project) }
- let(:issuable) { create(:issue, project: project) }
+ context 'on a merge request' do
+ let(:container) { find('.detail-page-header-actions') }
+ let(:project) { create(:project, :repository) }
+ let(:issuable) { create(:merge_request, source_project: project) }
before do
project.add_maintainer(user)
@@ -60,67 +19,38 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
context 'when user has permission to update', :js do
before do
- visit project_issue_path(project, issuable)
+ visit project_merge_request_path(project, issuable)
end
- it_behaves_like 'an issuable close/reopen/report toggle'
-
- context 'when the issue is closed and locked' do
- let(:issuable) { create(:issue, :closed, :locked, project: project) }
+ context 'close/reopen/report toggle' do
+ it 'opens a dropdown when toggle is clicked' do
+ click_button 'Toggle dropdown'
- it 'hides the reopen button' do
- expect(page).not_to have_button('Reopen issue')
+ expect(container).to have_link("Close merge request")
+ expect(container).to have_link('Report abuse')
+ expect(container).to have_text("Report merge requests that are abusive, inappropriate or spam.")
end
- context 'when the issue author is the current user' do
- before do
- issuable.update(author: user)
- end
+ it 'links to Report Abuse' do
+ click_button 'Toggle dropdown'
+ click_link 'Report abuse'
- it 'hides the reopen button' do
- expect(page).not_to have_button('Reopen issue')
- end
+ expect(page).to have_content('Report abuse to admin')
end
end
- end
-
- context 'when user doesnt have permission to update' do
- let(:cant_project) { create(:project) }
- let(:cant_issuable) { create(:issue, project: cant_project) }
-
- before do
- cant_project.add_guest(user)
-
- visit project_issue_path(cant_project, cant_issuable)
- end
-
- it 'only shows the `Report abuse` and `New issue` buttons' do
- expect(page).to have_link('Report abuse')
- expect(page).to have_link('New issue')
- expect(page).not_to have_button('Close issue')
- expect(page).not_to have_button('Reopen issue')
- expect(page).not_to have_link(title: 'Edit title and description')
- end
- end
- end
-
- context 'on a merge request' do
- let(:container) { find('.detail-page-header-actions') }
- let(:project) { create(:project, :repository) }
- let(:issuable) { create(:merge_request, source_project: project) }
- before do
- project.add_maintainer(user)
- login_as user
- end
+ context 'when the merge request is open' do
+ let(:issuable) { create(:merge_request, :opened, source_project: project) }
- context 'when user has permission to update', :js do
- before do
- visit project_merge_request_path(project, issuable)
+ it 'shows the `Edit` and `Mark as draft` buttons' do
+ expect(container).to have_link('Edit')
+ expect(container).to have_link('Mark as draft')
+ expect(container).not_to have_button('Report abuse')
+ expect(container).not_to have_button('Close merge request')
+ expect(container).not_to have_link('Reopen merge request')
+ end
end
- it_behaves_like 'an issuable close/reopen/report toggle'
-
context 'when the merge request is closed' do
let(:issuable) { create(:merge_request, :closed, source_project: project) }
diff --git a/spec/features/issuables/merge_request_discussion_lock_spec.rb b/spec/features/merge_request/merge_request_discussion_lock_spec.rb
index 4e0265839f6..4e0265839f6 100644
--- a/spec/features/issuables/merge_request_discussion_lock_spec.rb
+++ b/spec/features/merge_request/merge_request_discussion_lock_spec.rb
diff --git a/spec/features/merge_request/user_closes_merge_request_spec.rb b/spec/features/merge_request/user_closes_merge_request_spec.rb
deleted file mode 100644
index e6b6778c76e..00000000000
--- a/spec/features/merge_request/user_closes_merge_request_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'User closes a merge requests', :js do
- let(:project) { create(:project, :repository) }
- let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- sign_in(user)
-
- visit(merge_request_path(merge_request))
- end
-
- it 'closes a merge request' do
- click_button('Close merge request', match: :first)
-
- expect(page).to have_content(merge_request.title)
- expect(page).to have_content('Closed by')
- end
-end
diff --git a/spec/features/merge_request/user_closes_reopens_merge_request_state_spec.rb b/spec/features/merge_request/user_closes_reopens_merge_request_state_spec.rb
new file mode 100644
index 00000000000..6376f9ab5fd
--- /dev/null
+++ b/spec/features/merge_request/user_closes_reopens_merge_request_state_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'User closes/reopens a merge request', :js do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ describe 'when open' do
+ context 'when clicking the top `Close merge request` link', :aggregate_failures do
+ let(:open_merge_request) { create(:merge_request, source_project: project, target_project: project) }
+
+ before do
+ visit merge_request_path(open_merge_request)
+ end
+
+ it 'can close a merge request' do
+ expect(find('.status-box')).to have_content 'Open'
+
+ within '.detail-page-header' do
+ click_button 'Toggle dropdown'
+ click_link 'Close merge request'
+ end
+
+ wait_for_requests
+
+ expect(find('.status-box')).to have_content 'Closed'
+ end
+ end
+
+ context 'when clicking the bottom `Close merge request` button', :aggregate_failures do
+ let(:open_merge_request) { create(:merge_request, source_project: project, target_project: project) }
+
+ before do
+ visit merge_request_path(open_merge_request)
+ end
+
+ it 'can close a merge request' do
+ expect(find('.status-box')).to have_content 'Open'
+
+ within '.timeline-content-form' do
+ click_button 'Close merge request'
+
+ # Clicking the bottom `Close merge request` button does not yet update
+ # the header status so for now we'll check that the button text changes
+ expect(page).not_to have_button 'Close merge request'
+ expect(page).to have_button 'Reopen merge request'
+ end
+ end
+ end
+ end
+
+ describe 'when closed' do
+ context 'when clicking the top `Reopen merge request` link', :aggregate_failures do
+ let(:closed_merge_request) { create(:merge_request, source_project: project, target_project: project, state: 'closed') }
+
+ before do
+ visit merge_request_path(closed_merge_request)
+ end
+
+ it 'can reopen a merge request' do
+ expect(find('.status-box')).to have_content 'Closed'
+
+ within '.detail-page-header' do
+ click_button 'Toggle dropdown'
+ click_link 'Reopen merge request'
+ end
+
+ wait_for_requests
+
+ expect(find('.status-box')).to have_content 'Open'
+ end
+ end
+
+ context 'when clicking the bottom `Reopen merge request` button', :aggregate_failures do
+ let(:closed_merge_request) { create(:merge_request, source_project: project, target_project: project, state: 'closed') }
+
+ before do
+ visit merge_request_path(closed_merge_request)
+ end
+
+ it 'can reopen a merge request' do
+ expect(find('.status-box')).to have_content 'Closed'
+
+ within '.timeline-content-form' do
+ click_button 'Reopen merge request'
+
+ # Clicking the bottom `Reopen merge request` button does not yet update
+ # the header status so for now we'll check that the button text changes
+ expect(page).not_to have_button 'Reopen merge request'
+ expect(page).to have_button 'Close merge request'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb
index c452408cff2..0fd140a00bd 100644
--- a/spec/features/merge_request/user_comments_on_diff_spec.rb
+++ b/spec/features/merge_request/user_comments_on_diff_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe 'User comments on a diff', :js do
click_button('Add comment now')
end
- page.within('.diff-files-holder > div:nth-child(3)') do
+ page.within('.diff-files-holder > div:nth-child(6)') do
expect(page).to have_content('Line is wrong')
find('.js-diff-more-actions').click
@@ -53,7 +53,7 @@ RSpec.describe 'User comments on a diff', :js do
wait_for_requests
- page.within('.diff-files-holder > div:nth-child(2) .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(5) .note-body > .note-text') do
expect(page).to have_content('Line is correct')
end
@@ -67,7 +67,7 @@ RSpec.describe 'User comments on a diff', :js do
wait_for_requests
# Hide the comment.
- page.within('.diff-files-holder > div:nth-child(3)') do
+ page.within('.diff-files-holder > div:nth-child(6)') do
find('.js-diff-more-actions').click
click_button 'Hide comments on this file'
@@ -76,22 +76,22 @@ RSpec.describe 'User comments on a diff', :js do
# At this moment a user should see only one comment.
# The other one should be hidden.
- page.within('.diff-files-holder > div:nth-child(2) .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(5) .note-body > .note-text') do
expect(page).to have_content('Line is correct')
end
# Show the comment.
- page.within('.diff-files-holder > div:nth-child(3)') do
+ page.within('.diff-files-holder > div:nth-child(6)') do
find('.js-diff-more-actions').click
click_button 'Show comments on this file'
end
# Now both the comments should be shown.
- page.within('.diff-files-holder > div:nth-child(3) .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(6) .note-body > .note-text') do
expect(page).to have_content('Line is wrong')
end
- page.within('.diff-files-holder > div:nth-child(2) .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(5) .note-body > .note-text') do
expect(page).to have_content('Line is correct')
end
@@ -102,11 +102,11 @@ RSpec.describe 'User comments on a diff', :js do
wait_for_requests
- page.within('.diff-files-holder > div:nth-child(3) .parallel .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(6) .parallel .note-body > .note-text') do
expect(page).to have_content('Line is wrong')
end
- page.within('.diff-files-holder > div:nth-child(2) .parallel .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(5) .parallel .note-body > .note-text') do
expect(page).to have_content('Line is correct')
end
end
@@ -136,7 +136,7 @@ RSpec.describe 'User comments on a diff', :js do
add_comment('-13', '+15')
end
- it 'allows comments on previously hidden lines at the top of a file' do
+ it 'allows comments on previously hidden lines at the top of a file', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/285294' do
# Click -9, expand up, select 1 add and verify comment
page.within('[data-path="files/ruby/popen.rb"]') do
all('.js-unfold-all')[0].click
@@ -204,7 +204,7 @@ RSpec.describe 'User comments on a diff', :js do
click_button('Add comment now')
end
- page.within('.diff-file:nth-of-type(5) .discussion .note') do
+ page.within('.diff-file:nth-of-type(1) .discussion .note') do
find('.js-note-edit').click
page.within('.current-note-edit-form') do
@@ -215,7 +215,7 @@ RSpec.describe 'User comments on a diff', :js do
expect(page).not_to have_button('Save comment', disabled: true)
end
- page.within('.diff-file:nth-of-type(5) .discussion .note') do
+ page.within('.diff-file:nth-of-type(1) .discussion .note') do
expect(page).to have_content('Typo, please fix').and have_no_content('Line is wrong')
end
end
@@ -234,7 +234,7 @@ RSpec.describe 'User comments on a diff', :js do
expect(page).to have_content('1')
end
- page.within('.diff-file:nth-of-type(5) .discussion .note') do
+ page.within('.diff-file:nth-of-type(1) .discussion .note') do
find('.more-actions').click
find('.more-actions .dropdown-menu li', match: :first)
diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index 9556142ecb8..794dfd7c8da 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -74,7 +74,10 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
context 'with an unfolded line' do
before do
- find('.js-unfold', match: :first).click
+ page.within('.file-holder[id="a5cc2925ca8258af241be7e5b0381edf30266302"]') do
+ find('.js-unfold', match: :first).click
+ end
+
wait_for_requests
end
@@ -137,7 +140,10 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
context 'with an unfolded line' do
before do
- find('.js-unfold', match: :first).click
+ page.within('.file-holder[id="a5cc2925ca8258af241be7e5b0381edf30266302"]') do
+ find('.js-unfold', match: :first).click
+ end
+
wait_for_requests
end
diff --git a/spec/features/merge_request/user_reopens_merge_request_spec.rb b/spec/features/merge_request/user_reopens_merge_request_spec.rb
deleted file mode 100644
index 4a05a3be59a..00000000000
--- a/spec/features/merge_request/user_reopens_merge_request_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'User reopens a merge requests', :js do
- let(:project) { create(:project, :public, :repository) }
- let!(:merge_request) { create(:closed_merge_request, source_project: project, target_project: project) }
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- sign_in(user)
-
- visit(merge_request_path(merge_request))
- end
-
- it 'reopens a merge request' do
- find('.js-issuable-close-dropdown .dropdown-toggle').click
-
- click_link('Reopen merge request', match: :first)
-
- wait_for_requests
-
- page.within('.status-box') do
- expect(page).to have_content('Open')
- end
- end
-end
diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb
index 06405232462..1b1152897fc 100644
--- a/spec/features/merge_request/user_resolves_conflicts_spec.rb
+++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb
@@ -8,11 +8,6 @@ RSpec.describe 'Merge request > User resolves conflicts', :js do
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
- before do
- # In order to have the diffs collapsed, we need to disable the increase feature
- stub_feature_flags(gitlab_git_diff_size_limit_increase: false)
- end
-
def create_merge_request(source_branch)
create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr|
mr.mark_as_unmergeable
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 00f0c88497b..cb7c952dfe4 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
@@ -111,7 +111,6 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'shows resolved thread when toggled' do
find(".timeline-content .discussion[data-discussion-id='#{note.discussion_id}'] .discussion-toggle-button").click
- expect(page.find(".line-holder-placeholder")).to be_visible
expect(page.find(".timeline-content #note_#{note.id}")).to be_visible
end
diff --git a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
index e47f9ff2660..38546fd629d 100644
--- a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
+++ b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
@@ -18,8 +18,8 @@ RSpec.describe 'Merge request > User sees check out branch modal', :js do
expect(page).to have_content('Check out, review, and merge locally')
end
- it 'closes the check out branch modal with escape keypress' do
- find('#modal_merge_info').send_keys(:escape)
+ it 'closes the check out branch modal with the close action' do
+ find('.modal button[aria-label="Close"]').click
expect(page).not_to have_content('Check out, review, and merge locally')
end
diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
index 7b319f6aff8..6647a4e9291 100644
--- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
@@ -27,7 +27,6 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
before do
stub_application_setting(auto_devops_enabled: false)
- stub_feature_flags(ci_merge_request_pipeline: true)
stub_ci_pipeline_yaml_file(YAML.dump(config))
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index fb616ceae9d..8930c55a28c 100644
--- a/spec/features/merge_request/user_sees_versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -17,6 +17,8 @@ RSpec.describe 'Merge request > User sees versions', :js do
let!(:params) { {} }
before do
+ stub_feature_flags(diffs_gradual_load: false)
+
project.add_maintainer(user)
sign_in(user)
visit diffs_project_merge_request_path(project, merge_request, params)
@@ -73,12 +75,12 @@ RSpec.describe 'Merge request > User sees versions', :js do
it 'shows the commit SHAs for every version in the dropdown' do
page.within '.mr-version-dropdown' do
- find('.btn-default').click
+ find('.gl-dropdown-toggle').click
+ end
- page.within('.dropdown-content') do
- shas = merge_request.merge_request_diffs.map { |diff| Commit.truncate_sha(diff.head_commit_sha) }
- shas.each { |sha| expect(page).to have_content(sha) }
- end
+ page.within '.mr-version-dropdown' do
+ shas = merge_request.merge_request_diffs.map { |diff| Commit.truncate_sha(diff.head_commit_sha) }
+ shas.each { |sha| expect(page).to have_content(sha) }
end
end
@@ -182,7 +184,7 @@ RSpec.describe 'Merge request > User sees versions', :js do
it 'has 0 chages between versions' do
page.within '.mr-version-compare-dropdown' do
- expect(find('.dropdown-menu-toggle')).to have_content 'version 1'
+ expect(find('.gl-dropdown-toggle')).to have_content 'version 1'
end
page.within '.mr-version-dropdown' do
@@ -203,7 +205,7 @@ RSpec.describe 'Merge request > User sees versions', :js do
it 'sets the compared versions to be the same' do
page.within '.mr-version-compare-dropdown' do
- expect(find('.dropdown-menu-toggle')).to have_content 'version 2'
+ expect(find('.gl-dropdown-toggle')).to have_content 'version 2'
end
page.within '.mr-version-dropdown' do
diff --git a/spec/features/merge_requests/user_squashes_merge_request_spec.rb b/spec/features/merge_request/user_squashes_merge_request_spec.rb
index 84964bd0637..84964bd0637 100644
--- a/spec/features/merge_requests/user_squashes_merge_request_spec.rb
+++ b/spec/features/merge_request/user_squashes_merge_request_spec.rb
diff --git a/spec/features/merge_requests/user_views_diffs_commit_spec.rb b/spec/features/merge_request/user_views_diffs_commit_spec.rb
index cf92603972e..cf92603972e 100644
--- a/spec/features/merge_requests/user_views_diffs_commit_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_commit_spec.rb
diff --git a/spec/features/merge_request/user_views_diffs_file_by_file_spec.rb b/spec/features/merge_request/user_views_diffs_file_by_file_spec.rb
index bb4bf0864c9..ad9c342df3e 100644
--- a/spec/features/merge_request/user_views_diffs_file_by_file_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_file_by_file_spec.rb
@@ -23,12 +23,12 @@ RSpec.describe 'User views diffs file-by-file', :js do
it 'shows diffs file-by-file' do
page.within('#diffs') do
expect(page).to have_selector('.file-holder', count: 1)
- expect(page).to have_selector('.diff-file .file-title', text: '.DS_Store')
+ expect(page).to have_selector('.diff-file .file-title', text: 'files/ruby/popen.rb')
find('.page-link.next-page-item').click
expect(page).to have_selector('.file-holder', count: 1)
- expect(page).to have_selector('.diff-file .file-title', text: '.gitignore')
+ expect(page).to have_selector('.diff-file .file-title', text: 'files/ruby/regex.rb')
end
end
end
diff --git a/spec/features/merge_request/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb
index e1865fe2e14..a0b3067994c 100644
--- a/spec/features/merge_request/user_views_diffs_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_spec.rb
@@ -22,8 +22,8 @@ RSpec.describe 'User views diffs', :js do
it 'unfolds diffs upwards' do
first('.js-unfold').click
- page.within('.file-holder[id="a5cc2925ca8258af241be7e5b0381edf30266302"]') do
- expect(find('.text-file')).to have_content('.bundle')
+ page.within('.file-holder[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd"]') do
+ expect(find('.text-file')).to have_content('fileutils')
expect(page).to have_selector('.new_line [data-linenumber="1"]', count: 1)
end
end
diff --git a/spec/features/merge_request/user_sees_empty_state_spec.rb b/spec/features/merge_requests/user_sees_empty_state_spec.rb
index ac07b31731d..ac07b31731d 100644
--- a/spec/features/merge_request/user_sees_empty_state_spec.rb
+++ b/spec/features/merge_requests/user_sees_empty_state_spec.rb
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index dce76e4df6d..b9594293996 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -101,7 +101,7 @@ RSpec.describe 'Milestone' do
end
describe 'Deleting a milestone' do
- it "The delete milestone button does not show for unauthorized users" do
+ it "the delete milestone button does not show for unauthorized users" do
create(:milestone, project: project, title: 8.7)
sign_out(user)
diff --git a/spec/features/operations_sidebar_link_spec.rb b/spec/features/operations_sidebar_link_spec.rb
index 32e2833dafb..798f9092db0 100644
--- a/spec/features/operations_sidebar_link_spec.rb
+++ b/spec/features/operations_sidebar_link_spec.rb
@@ -2,31 +2,88 @@
require 'spec_helper'
-RSpec.describe 'Operations dropdown sidebar' do
- let_it_be(:project) { create(:project, :repository) }
+RSpec.describe 'Operations dropdown sidebar', :aggregate_failures do
+ let_it_be_with_reload(:project) { create(:project, :internal, :repository) }
let(:user) { create(:user) }
+ let(:access_level) { ProjectFeature::PUBLIC }
+ let(:role) { nil }
before do
- project.add_role(user, role)
+ project.add_role(user, role) if role
+ project.project_feature.update_attribute(:operations_access_level, access_level)
+
sign_in(user)
visit project_issues_path(project)
end
+ shared_examples 'shows Operations menu based on the access level' do
+ context 'when operations project feature is PRIVATE' do
+ let(:access_level) { ProjectFeature::PRIVATE }
+
+ it 'shows the `Operations` menu' do
+ expect(page).to have_selector('a.shortcuts-operations', text: 'Operations')
+ end
+ end
+
+ context 'when operations project feature is DISABLED' do
+ let(:access_level) { ProjectFeature::DISABLED }
+
+ it 'does not show the `Operations` menu' do
+ expect(page).not_to have_selector('a.shortcuts-operations')
+ end
+ end
+ end
+
+ context 'user is not a member' do
+ it 'has the correct `Operations` menu items', :aggregate_failures do
+ expect(page).to have_selector('a.shortcuts-operations', text: 'Operations')
+ expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project))
+ expect(page).to have_link(title: 'Environments', href: project_environments_path(project))
+
+ expect(page).not_to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project))
+ expect(page).not_to have_link(title: 'Alerts', href: project_alert_management_index_path(project))
+ expect(page).not_to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project))
+ expect(page).not_to have_link(title: 'Product Analytics', href: project_product_analytics_path(project))
+ expect(page).not_to have_link(title: 'Serverless', href: project_serverless_functions_path(project))
+ expect(page).not_to have_link(title: 'Logs', href: project_logs_path(project))
+ expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project))
+ end
+
+ context 'when operations project feature is PRIVATE' do
+ let(:access_level) { ProjectFeature::PRIVATE }
+
+ it 'does not show the `Operations` menu' do
+ expect(page).not_to have_selector('a.shortcuts-operations')
+ end
+ end
+
+ context 'when operations project feature is DISABLED' do
+ let(:access_level) { ProjectFeature::DISABLED }
+
+ it 'does not show the `Operations` menu' do
+ expect(page).not_to have_selector('a.shortcuts-operations')
+ end
+ end
+ end
+
context 'user has guest role' do
let(:role) { :guest }
it 'has the correct `Operations` menu items' do
+ expect(page).to have_selector('a.shortcuts-operations', text: 'Operations')
expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project))
+ expect(page).to have_link(title: 'Environments', href: project_environments_path(project))
expect(page).not_to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project))
expect(page).not_to have_link(title: 'Alerts', href: project_alert_management_index_path(project))
- expect(page).not_to have_link(title: 'Environments', href: project_environments_path(project))
expect(page).not_to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project))
expect(page).not_to have_link(title: 'Product Analytics', href: project_product_analytics_path(project))
expect(page).not_to have_link(title: 'Serverless', href: project_serverless_functions_path(project))
expect(page).not_to have_link(title: 'Logs', href: project_logs_path(project))
expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project))
end
+
+ it_behaves_like 'shows Operations menu based on the access level'
end
context 'user has reporter role' do
@@ -44,6 +101,8 @@ RSpec.describe 'Operations dropdown sidebar' do
expect(page).not_to have_link(title: 'Logs', href: project_logs_path(project))
expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project))
end
+
+ it_behaves_like 'shows Operations menu based on the access level'
end
context 'user has developer role' do
@@ -61,6 +120,8 @@ RSpec.describe 'Operations dropdown sidebar' do
expect(page).not_to have_link(title: 'Serverless', href: project_serverless_functions_path(project))
expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project))
end
+
+ it_behaves_like 'shows Operations menu based on the access level'
end
context 'user has maintainer role' do
@@ -77,5 +138,7 @@ RSpec.describe 'Operations dropdown sidebar' do
expect(page).to have_link(title: 'Logs', href: project_logs_path(project))
expect(page).to have_link(title: 'Kubernetes', href: project_clusters_path(project))
end
+
+ it_behaves_like 'shows Operations menu based on the access level'
end
end
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 62d8a96c1b2..2e8d9ef80cd 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -78,14 +78,14 @@ RSpec.describe 'Profile > Account', :js do
update_username(new_username)
visit new_project_path
expect(current_path).to eq(new_project_path)
- expect(find('.breadcrumbs-sub-title')).to have_content('Details')
+ expect(find('.breadcrumbs')).to have_content(user.name)
end
it 'the old project path redirects to the new path' do
update_username(new_username)
visit old_project_path
expect(current_path).to eq(new_project_path)
- expect(find('.breadcrumbs-sub-title')).to have_content('Details')
+ expect(find('.breadcrumbs')).to have_content(user.name)
end
end
end
diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb
index 75531d43df2..fd64704b7c8 100644
--- a/spec/features/profiles/active_sessions_spec.rb
+++ b/spec/features/profiles/active_sessions_spec.rb
@@ -11,8 +11,8 @@ RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
let(:admin) { create(:admin) }
- it 'User sees their active sessions' do
- Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do
+ it 'user sees their active sessions' do
+ travel_to(Time.zone.parse('2018-03-12 09:06')) do
Capybara::Session.new(:session1)
Capybara::Session.new(:session2)
Capybara::Session.new(:session3)
@@ -45,6 +45,7 @@ RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
)
gitlab_sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
visit admin_user_path(user)
@@ -55,8 +56,8 @@ RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
visit profile_active_sessions_path
expect(page).to(
- have_selector('ul.list-group li.list-group-item', { text: 'Signed in on',
- count: 2 }))
+ have_selector('ul.list-group li.list-group-item', text: 'Signed in on',
+ count: 2))
expect(page).to have_content(
'127.0.0.1 ' \
@@ -81,7 +82,7 @@ RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
end
end
- it 'User can revoke a session', :js do
+ it 'user can revoke a session', :js do
Capybara::Session.new(:session1)
Capybara::Session.new(:session2)
diff --git a/spec/features/profiles/emails_spec.rb b/spec/features/profiles/emails_spec.rb
index fc7de6d8b23..bdf1f8b022a 100644
--- a/spec/features/profiles/emails_spec.rb
+++ b/spec/features/profiles/emails_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe 'Profile > Emails' do
end
end
- it 'User removes email' do
+ it 'user removes email' do
user.emails.create(email: 'my@email.com')
visit profile_emails_path
expect(page).to have_content("my@email.com")
@@ -51,7 +51,7 @@ RSpec.describe 'Profile > Emails' do
expect(page).not_to have_content("my@email.com")
end
- it 'User confirms email' do
+ it 'user confirms email' do
email = user.emails.create(email: 'my@email.com')
visit profile_emails_path
expect(page).to have_content("#{email.email} Unverified")
@@ -63,7 +63,7 @@ RSpec.describe 'Profile > Emails' do
expect(page).to have_content("#{email.email} Verified")
end
- it 'User re-sends confirmation email' do
+ it 'user re-sends confirmation email' do
email = user.emails.create(email: 'my@email.com')
visit profile_emails_path
diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb
index 18ed4e646b3..4eedeeac262 100644
--- a/spec/features/profiles/gpg_keys_spec.rb
+++ b/spec/features/profiles/gpg_keys_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe 'Profile > GPG Keys' do
end
end
- it 'User sees their key' do
+ it 'user sees their key' do
create(:gpg_key, user: user, key: GpgHelpers::User2.public_key)
visit profile_gpg_keys_path
@@ -45,7 +45,7 @@ RSpec.describe 'Profile > GPG Keys' do
expect(page).to have_content(GpgHelpers::User2.fingerprint)
end
- it 'User removes a key via the key index' do
+ it 'user removes a key via the key index' do
create(:gpg_key, user: user, key: GpgHelpers::User2.public_key)
visit profile_gpg_keys_path
@@ -54,7 +54,7 @@ RSpec.describe 'Profile > GPG Keys' do
expect(page).to have_content('Your GPG keys (0)')
end
- it 'User revokes a key via the key index' do
+ it 'user revokes a key via the key index' do
gpg_key = create :gpg_key, user: user, key: GpgHelpers::User2.public_key
gpg_signature = create :gpg_signature, gpg_key: gpg_key, verification_status: :verified
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
index 23bbe9c1587..c1e2d19ad9a 100644
--- a/spec/features/profiles/keys_spec.rb
+++ b/spec/features/profiles/keys_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe 'Profile > SSH Keys' do
end
end
- it 'User sees their keys' do
+ it 'user sees their keys' do
key = create(:key, user: user)
visit profile_keys_path
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index de5a594aca6..88bfc71cfbe 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -18,6 +18,10 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
find("#created-personal-access-token").value
end
+ def feed_token
+ find("#feed_token").value
+ end
+
def disallow_personal_access_token_saves!
allow(PersonalAccessTokens::CreateService).to receive(:new).and_return(pat_create_service)
@@ -112,4 +116,26 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
end
end
end
+
+ describe "feed token" do
+ context "when enabled" do
+ it "displays feed token" do
+ allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(false)
+ visit profile_personal_access_tokens_path
+
+ expect(page).to have_content("Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar, and is included in those feed URLs.")
+ expect(feed_token).to eq(user.feed_token)
+ end
+ end
+
+ context "when disabled" do
+ it "does not display feed token" do
+ allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true)
+ visit profile_personal_access_tokens_path
+
+ expect(page).not_to have_content("Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar, and is included in those feed URLs.")
+ expect(page).not_to have_css("#feed_token")
+ end
+ end
+ end
end
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
index a5b7b1fba9d..1b6215c1308 100644
--- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
+++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe 'Profile > Notifications > User changes notified_of_own_activity
sign_in(user)
end
- it 'User opts into receiving notifications about their own activity' do
+ it 'user opts into receiving notifications about their own activity' do
visit profile_notifications_path
expect(page).not_to have_checked_field('user[notified_of_own_activity]')
@@ -20,7 +20,7 @@ RSpec.describe 'Profile > Notifications > User changes notified_of_own_activity
expect(page).to have_checked_field('user[notified_of_own_activity]')
end
- it 'User opts out of receiving notifications about their own activity' do
+ it 'user opts out of receiving notifications about their own activity' do
user.update!(notified_of_own_activity: true)
visit profile_notifications_path
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index d0340dfc880..239bc04a9cb 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'User edit profile' do
+ include Spec::Support::Helpers::Features::NotesHelpers
+
let(:user) { create(:user) }
before do
@@ -226,7 +228,7 @@ RSpec.describe 'User edit profile' do
end
def open_edit_status_modal
- open_modal 'Edit status'
+ open_modal 'Edit status'
end
def set_user_status_in_modal
@@ -289,6 +291,10 @@ RSpec.describe 'User edit profile' do
toggle_busy_status
set_user_status_in_modal
+
+ wait_for_requests
+ visit root_path(user)
+
open_edit_status_modal
expect(busy_status.checked?).to eq(true)
@@ -366,26 +372,37 @@ RSpec.describe 'User edit profile' do
expect(page).not_to have_selector '.cover-status'
end
- it 'clears the user status with the "Remove status" button' do
- user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread')
+ context 'Remove status button' do
+ before do
+ user.status = UserStatus.new(message: 'Eating bread', emoji: 'stuffed_flatbread')
- visit_user
- wait_for_requests
+ visit_user
+ wait_for_requests
- within('.cover-status') do
- expect(page).to have_emoji(user_status.emoji)
- expect(page).to have_content user_status.message
+ open_edit_status_modal
+
+ page.within "#set-user-status-modal" do
+ click_button 'Remove status'
+ end
+
+ wait_for_requests
end
- open_edit_status_modal
+ it 'clears the user status with the "Remove status" button' do
+ visit_user
- page.within "#set-user-status-modal" do
- click_button 'Remove status'
+ expect(page).not_to have_selector '.cover-status'
end
- visit_user
+ it 'shows the "Set status" menu item in the user menu' do
+ visit root_path(user)
- expect(page).not_to have_selector '.cover-status'
+ find('.header-user-dropdown-toggle').click
+
+ page.within ".header-user" do
+ expect(page).to have_content('Set status')
+ end
+ end
end
it 'displays a default emoji if only message is entered' do
@@ -398,6 +415,45 @@ RSpec.describe 'User edit profile' do
end
end
+ context 'note header' do
+ let(:project) { create(:project_empty_repo, :public) }
+ let(:issue) { create(:issue, project: project) }
+ let(:emoji) { "stuffed_flatbread" }
+
+ before do
+ project.add_guest(user)
+ create(:user_status, user: user, message: 'Taking notes', emoji: emoji)
+
+ visit(project_issue_path(project, issue))
+
+ add_note("This is a comment")
+ visit(project_issue_path(project, issue))
+
+ wait_for_requests
+ end
+
+ it 'displays the status emoji' do
+ first_note = page.find_all(".main-notes-list .timeline-entry").first
+
+ expect(first_note).to have_emoji(emoji)
+ end
+
+ it 'clears the status emoji' do
+ open_edit_status_modal
+
+ page.within "#set-user-status-modal" do
+ click_button 'Remove status'
+ end
+
+ visit(project_issue_path(project, issue))
+ wait_for_requests
+
+ first_note = page.find_all(".main-notes-list .timeline-entry").first
+
+ expect(first_note).not_to have_css('.user-status-emoji')
+ end
+ end
+
context 'with set_user_availability_status feature flag disabled' do
before do
stub_feature_flags(set_user_availability_status: false)
diff --git a/spec/features/projects/active_tabs_spec.rb b/spec/features/projects/active_tabs_spec.rb
index 349e5f5e177..8001ce0f454 100644
--- a/spec/features/projects/active_tabs_spec.rb
+++ b/spec/features/projects/active_tabs_spec.rb
@@ -124,15 +124,15 @@ RSpec.describe 'Project active tab' do
context 'on project Analytics' do
before do
- visit charts_project_graph_path(project, 'master')
+ visit project_cycle_analytics_path(project)
end
- context 'on project Analytics/Repository Analytics' do
+ context 'on project Analytics/Value Stream Analytics' do
it_behaves_like 'page has active tab', _('Analytics')
- it_behaves_like 'page has active sub tab', _('Repository')
+ it_behaves_like 'page has active sub tab', _('Value Stream')
end
- context 'on project Analytics/Cycle Analytics' do
+ context 'on project Analytics/"CI / CD"' do
before do
click_tab(_('CI / CD'))
end
diff --git a/spec/features/projects/blobs/balsamiq_spec.rb b/spec/features/projects/blobs/balsamiq_spec.rb
new file mode 100644
index 00000000000..bce60856544
--- /dev/null
+++ b/spec/features/projects/blobs/balsamiq_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Balsamiq file blob', :js do
+ let(:project) { create(:project, :public, :repository) }
+
+ before do
+ visit project_blob_path(project, 'add-balsamiq-file/files/images/balsamiq.bmpr')
+
+ wait_for_requests
+ end
+
+ it 'displays Balsamiq file content' do
+ expect(page).to have_content("Mobile examples")
+ end
+end
diff --git a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
index 6b9fd41059d..484f740faee 100644
--- a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
+++ b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
@@ -9,12 +9,9 @@ RSpec.describe 'User creates new blob', :js do
let(:project) { create(:project, :empty_repo) }
shared_examples 'creating a file' do
- before do
- sign_in(user)
+ it 'allows the user to add a new file in Web IDE' do
visit project_path(project)
- end
- it 'allows the user to add a new file in Web IDE' do
click_link 'New file'
wait_for_requests
@@ -31,6 +28,7 @@ RSpec.describe 'User creates new blob', :js do
describe 'as a maintainer' do
before do
project.add_maintainer(user)
+ sign_in(user)
end
it_behaves_like 'creating a file'
@@ -39,6 +37,11 @@ RSpec.describe 'User creates new blob', :js do
describe 'as an admin' do
let(:user) { create(:user, :admin) }
+ before do
+ sign_in(user)
+ gitlab_enable_admin_mode_sign_in(user)
+ end
+
it_behaves_like 'creating a file'
end
diff --git a/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb b/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb
index 3069405ba63..1c79b2ddc38 100644
--- a/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb
+++ b/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb
@@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe 'User follows pipeline suggest nudge spec when feature is enabled', :js do
include CookieHelper
- let(:user) { create(:user, :admin) }
let(:project) { create(:project, :empty_repo) }
+ let(:user) { project.owner }
describe 'viewing the new blob page' do
before do
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index a0519d88532..d34dde6a8f2 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
+RSpec.describe 'Gcp Cluster', :js do
include GoogleApi::CloudPlatformHelpers
let(:project) { create(:project) }
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index 6c6e65005f6..6da66989b09 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -110,7 +110,7 @@ RSpec.describe 'Clusters', :js do
visit project_clusters_path(project)
end
- it 'user sees a add cluster button ' do
+ it 'user sees a add cluster button' do
expect(page).to have_selector('.js-add-cluster:not(.readonly)')
end
diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb
index 45bf35a6aab..ee453aa7bbf 100644
--- a/spec/features/projects/container_registry_spec.rb
+++ b/spec/features/projects/container_registry_spec.rb
@@ -94,7 +94,8 @@ RSpec.describe 'Container Registry', :js do
end
it('pagination navigate to the second page') do
- visit_second_page
+ visit_next_page
+
expect(page).to have_content '20'
end
end
@@ -116,22 +117,23 @@ RSpec.describe 'Container Registry', :js do
context 'when there are more than 10 images' do
before do
- create_list(:container_repository, 12, project: project)
project.container_repositories << container_repository
+ create_list(:container_repository, 12, project: project)
+
visit_container_registry
end
it 'shows pagination' do
- expect(page).to have_css '.gl-pagination'
+ expect(page).to have_css '.gl-keyset-pagination'
end
it 'pagination goes to second page' do
- visit_second_page
+ visit_next_page
expect(page).to have_content 'my/image'
end
it 'pagination is preserved after navigating back from details' do
- visit_second_page
+ visit_next_page
click_link 'my/image'
breadcrumb = find '.breadcrumbs'
breadcrumb.click_link 'Container Registry'
@@ -148,8 +150,8 @@ RSpec.describe 'Container Registry', :js do
click_link name
end
- def visit_second_page
- pagination = find '.gl-pagination'
- pagination.click_link '2'
+ def visit_next_page
+ pagination = find '.gl-keyset-pagination'
+ pagination.click_button 'Next'
end
end
diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb
index 19f111a727b..747277e2562 100644
--- a/spec/features/projects/diffs/diff_show_spec.rb
+++ b/spec/features/projects/diffs/diff_show_spec.rb
@@ -155,10 +155,6 @@ RSpec.describe 'Diff file viewer', :js do
context 'binary file that appears to be text in the first 1024 bytes' do
before do
- # The file we're visiting is smaller than 10 KB and we want it collapsed
- # so we need to disable the size increase feature.
- stub_feature_flags(gitlab_git_diff_size_limit_increase: false)
-
visit_commit('7b1cf4336b528e0f3d1d140ee50cafdbc703597c')
end
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
index 8315c821b6d..e8f197b67c2 100644
--- a/spec/features/projects/environments/environment_metrics_spec.rb
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe 'Environment > Metrics' do
end
around do |example|
- Timecop.freeze(current_time) { example.run }
+ travel_to(current_time) { example.run }
end
shared_examples 'has environment selector' do
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 8c032660726..27167f95104 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -12,8 +12,20 @@ RSpec.describe 'Environments page', :js do
sign_in(user)
end
+ def actions_button_selector
+ '[data-testid="environment-actions-button"]'
+ end
+
+ def action_link_selector
+ '[data-testid="manual-action-link"]'
+ end
+
def stop_button_selector
- %q{button[title="Stop environment"]}
+ 'button[title="Stop environment"]'
+ end
+
+ def upcoming_deployment_content_selector
+ '[data-testid="upcoming-deployment-content"]'
end
describe 'page tabs' do
@@ -187,18 +199,17 @@ RSpec.describe 'Environments page', :js do
end
it 'shows a play button' do
- find('.js-environment-actions-dropdown').click
-
+ find(actions_button_selector).click
expect(page).to have_content(action.name)
end
it 'allows to play a manual action', :js do
expect(action).to be_manual
- find('.js-environment-actions-dropdown').click
+ find(actions_button_selector).click
expect(page).to have_content(action.name)
- expect { find('.js-manual-action-link').click }
+ expect { find(action_link_selector).click }
.not_to change { Ci::Pipeline.count }
end
@@ -301,11 +312,11 @@ RSpec.describe 'Environments page', :js do
end
it 'has a dropdown for actionable jobs' do
- expect(page).to have_selector('.dropdown-new.btn.btn-default [data-testid="play-icon"]')
+ expect(page).to have_selector("#{actions_button_selector} [data-testid=\"play-icon\"]")
end
it "has link to the delayed job's action" do
- find('.js-environment-actions-dropdown').click
+ find(actions_button_selector).click
expect(page).to have_button('delayed job')
expect(page).to have_content(/\d{2}:\d{2}:\d{2}/)
@@ -320,7 +331,7 @@ RSpec.describe 'Environments page', :js do
end
it "shows 00:00:00 as the remaining time" do
- find('.js-environment-actions-dropdown').click
+ find(actions_button_selector).click
expect(page).to have_content("00:00:00")
end
@@ -328,8 +339,8 @@ RSpec.describe 'Environments page', :js do
context 'when user played a delayed job immediately' do
before do
- find('.js-environment-actions-dropdown').click
- page.accept_confirm { click_button('delayed job') }
+ find(actions_button_selector).click
+ accept_confirm { find(action_link_selector).click }
wait_for_requests
end
@@ -355,6 +366,26 @@ RSpec.describe 'Environments page', :js do
expect(page).to have_content('No deployments yet')
end
end
+
+ context 'when there is an upcoming deployment' do
+ let_it_be(:project) { create(:project, :repository) }
+
+ let!(:deployment) do
+ create(:deployment, :running,
+ environment: environment,
+ sha: project.commit.id)
+ end
+
+ it "renders the upcoming deployment", :aggregate_failures do
+ visit_environments(project)
+
+ within(upcoming_deployment_content_selector) do
+ expect(page).to have_content("##{deployment.iid}")
+ expect(page).to have_selector("a[href=\"#{project_job_path(project, deployment.deployable)}\"]")
+ expect(page).to have_link(href: /#{deployment.user.username}/)
+ end
+ end
+ end
end
it 'does have a new environment button' do
@@ -423,10 +454,10 @@ RSpec.describe 'Environments page', :js do
expect(page).to have_content 'review-1'
expect(page).to have_content 'review-2'
within('.ci-table') do
- within('.gl-responsive-table-row:nth-child(3)') do
+ within('[data-qa-selector="environment_item"]', text: 'review-1') do
expect(find('.js-auto-stop').text).not_to be_empty
end
- within('.gl-responsive-table-row:nth-child(4)') do
+ within('[data-qa-selector="environment_item"]', text: 'review-2') do
expect(find('.js-auto-stop').text).not_to be_empty
end
end
diff --git a/spec/features/projects/feature_flags/user_creates_feature_flag_spec.rb b/spec/features/projects/feature_flags/user_creates_feature_flag_spec.rb
index 830dda737b0..eaafc7e607b 100644
--- a/spec/features/projects/feature_flags/user_creates_feature_flag_spec.rb
+++ b/spec/features/projects/feature_flags/user_creates_feature_flag_spec.rb
@@ -67,118 +67,6 @@ RSpec.describe 'User creates feature flag', :js do
end
end
- context 'with new version flags disabled' do
- before do
- stub_feature_flags(feature_flags_new_version: false)
- end
-
- context 'when creates without changing scopes' do
- before do
- visit(new_project_feature_flag_path(project))
- set_feature_flag_info('ci_live_trace', 'For live trace')
- click_button 'Create feature flag'
- expect(page).to have_current_path(project_feature_flags_path(project))
- end
-
- it 'shows the created feature flag' do
- within_feature_flag_row(1) do
- expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
- expect_status_toggle_button_to_be_checked
-
- within_feature_flag_scopes do
- expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*')
- end
- end
- end
- end
-
- context 'when creates with disabling the default scope' do
- before do
- visit(new_project_feature_flag_path(project))
- set_feature_flag_info('ci_live_trace', 'For live trace')
-
- within_scope_row(1) do
- within_status { find('.project-feature-toggle').click }
- end
-
- click_button 'Create feature flag'
- end
-
- it 'shows the created feature flag' do
- within_feature_flag_row(1) do
- expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
- expect_status_toggle_button_to_be_checked
-
- within_feature_flag_scopes do
- expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(1)')).to have_content('*')
- end
- end
- end
- end
-
- context 'when creates with an additional scope' do
- before do
- visit(new_project_feature_flag_path(project))
- set_feature_flag_info('mr_train', '')
-
- within_scope_row(2) do
- within_environment_spec do
- find('.js-env-search > input').set("review/*")
- find('.js-create-button').click
- end
- end
-
- within_scope_row(2) do
- within_status { find('.project-feature-toggle').click }
- end
-
- click_button 'Create feature flag'
- end
-
- it 'shows the created feature flag' do
- within_feature_flag_row(1) do
- expect(page.find('.feature-flag-name')).to have_content('mr_train')
- expect_status_toggle_button_to_be_checked
-
- within_feature_flag_scopes do
- expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*')
- expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(2)')).to have_content('review/*')
- end
- end
- end
- end
-
- context 'when searches an environment name for scope creation' do
- let!(:environment) { create(:environment, name: 'production', project: project) }
-
- before do
- visit(new_project_feature_flag_path(project))
- set_feature_flag_info('mr_train', '')
-
- within_scope_row(2) do
- within_environment_spec do
- find('.js-env-search > input').set('prod')
- click_button 'production'
- end
- end
-
- click_button 'Create feature flag'
- end
-
- it 'shows the created feature flag' do
- within_feature_flag_row(1) do
- expect(page.find('.feature-flag-name')).to have_content('mr_train')
- expect_status_toggle_button_to_be_checked
-
- within_feature_flag_scopes do
- expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*')
- expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(2)')).to have_content('production')
- end
- end
- end
- end
- end
-
private
def set_feature_flag_info(name, description)
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index 467adb25a17..2f0fbd29cb5 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'Edit Project Settings' do
sign_in(member)
end
- tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests" }
+ tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests", analytics: "analytics" }
tools.each do |tool_name, shortcut_name|
describe "feature #{tool_name}" do
@@ -150,6 +150,7 @@ RSpec.describe 'Edit Project Settings' do
before do
non_member.update_attribute(:admin, true)
sign_in(non_member)
+ gitlab_enable_admin_mode_sign_in(non_member)
end
it 'renders 404 if feature is disabled' do
diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb
index b02483be489..f4cd65bcba1 100644
--- a/spec/features/projects/gfm_autocomplete_load_spec.rb
+++ b/spec/features/projects/gfm_autocomplete_load_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe 'GFM autocomplete loading', :js do
let(:project) { create(:project) }
before do
- sign_in(create(:admin))
+ sign_in(project.owner)
visit project_path(project)
end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 83ceffa621c..af228764c17 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -28,73 +28,40 @@ RSpec.describe 'Import/Export - project import integration test', :js do
let(:project_name) { 'Test Project Name' + randomHex }
let(:project_path) { 'test-project-name' + randomHex }
- context 'prefilled the path' do
- it 'user imports an exported project successfully', :sidekiq_might_not_need_inline do
- visit new_project_path
+ it 'user imports an exported project successfully', :sidekiq_might_not_need_inline do
+ visit new_project_path
+ click_import_project_tab
+ click_link 'GitLab export'
- fill_in :project_name, with: project_name, visible: true
- click_import_project_tab
- click_link 'GitLab export'
+ fill_in :name, with: 'Test Project Name', visible: true
+ fill_in :path, with: 'test-project-path', visible: true
+ attach_file('file', file)
- expect(page).to have_content('Import an exported GitLab project')
- expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&name=#{ERB::Util.url_encode(project_name)}&path=#{project_path}")
+ expect { click_on 'Import project' }.to change { Project.count }.by(1)
- attach_file('file', file)
- click_on 'Import project'
-
- expect(Project.count).to eq(1)
-
- project = Project.last
- expect(project).not_to be_nil
- expect(project.description).to eq("Foo Bar")
- expect(project.issues).not_to be_empty
- expect(project.merge_requests).not_to be_empty
- expect(wiki_exists?(project)).to be true
- expect(project.import_state.status).to eq('finished')
- end
+ project = Project.last
+ expect(project).not_to be_nil
+ expect(page).to have_content("Project 'test-project-path' is being imported")
end
- context 'path is not prefilled' do
- it 'user imports an exported project successfully', :sidekiq_might_not_need_inline do
- visit new_project_path
- click_import_project_tab
- click_link 'GitLab export'
+ it 'invalid project' do
+ project = create(:project, namespace: user.namespace)
- fill_in :name, with: 'Test Project Name', visible: true
- fill_in :path, with: 'test-project-path', visible: true
- attach_file('file', file)
+ visit new_project_path
- expect { click_on 'Import project' }.to change { Project.count }.by(1)
+ click_import_project_tab
+ click_link 'GitLab export'
+ fill_in :name, with: project.name, visible: true
+ attach_file('file', file)
+ click_on 'Import project'
- project = Project.last
- expect(project).not_to be_nil
- expect(page).to have_content("Project 'test-project-path' is being imported")
+ page.within('.flash-container') do
+ expect(page).to have_content('Project could not be imported')
end
end
end
- it 'invalid project' do
- project = create(:project, namespace: user.namespace)
-
- visit new_project_path
-
- fill_in :project_name, with: project.name, visible: true
- click_import_project_tab
- click_link 'GitLab export'
- attach_file('file', file)
- click_on 'Import project'
-
- page.within('.flash-container') do
- expect(page).to have_content('Project could not be imported')
- end
- end
-
- def wiki_exists?(project)
- wiki = ProjectWiki.new(project)
- wiki.repository.exists? && !wiki.repository.empty?
- end
-
def click_import_project_tab
- find('#import-project-tab').click
+ find('[data-qa-selector="import_project_link"]').click
end
end
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
index 7f46a369dd6..b1e8127c54c 100644
--- a/spec/features/projects/jobs/permissions_spec.rb
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -3,11 +3,13 @@
require 'spec_helper'
RSpec.describe 'Project Jobs Permissions' do
- let(:user) { create(:user) }
- let(:group) { create(:group, name: 'some group') }
- let(:project) { create(:project, :repository, namespace: group) }
- let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
- let!(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) }
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be_with_reload(:group) { create(:group, name: 'some group') }
+ let_it_be_with_reload(:project) { create(:project, :repository, namespace: group) }
+ let_it_be_with_reload(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) }
before do
sign_in(user)
@@ -34,7 +36,7 @@ RSpec.describe 'Project Jobs Permissions' do
context 'when public access for jobs is disabled' do
before do
- project.update(public_builds: false)
+ project.update!(public_builds: false)
end
context 'when user is a guest' do
@@ -48,7 +50,7 @@ RSpec.describe 'Project Jobs Permissions' do
context 'when project is internal' do
before do
- project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
it_behaves_like 'recent job page details responds with status', 404
@@ -58,12 +60,30 @@ RSpec.describe 'Project Jobs Permissions' do
context 'when public access for jobs is enabled' do
before do
- project.update(public_builds: true)
+ project.update!(public_builds: true)
+ end
+
+ context 'when user is a guest' do
+ before do
+ project.add_guest(user)
+ end
+
+ it_behaves_like 'recent job page details responds with status', 200
+ it_behaves_like 'project jobs page responds with status', 200
+ end
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'recent job page details responds with status', 200
+ it_behaves_like 'project jobs page responds with status', 200
end
context 'when project is internal' do
before do
- project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
it_behaves_like 'recent job page details responds with status', 200 do
@@ -89,7 +109,7 @@ RSpec.describe 'Project Jobs Permissions' do
describe 'artifacts page' do
context 'when recent job has artifacts available' do
- before do
+ before_all do
archive = fixture_file_upload('spec/fixtures/ci_build_artifacts.zip')
create(:ci_job_artifact, :archive, file: archive, job: job)
@@ -97,7 +117,7 @@ RSpec.describe 'Project Jobs Permissions' do
context 'when public access for jobs is disabled' do
before do
- project.update(public_builds: false)
+ project.update!(public_builds: false)
end
context 'when user with guest role' do
@@ -128,4 +148,124 @@ RSpec.describe 'Project Jobs Permissions' do
end
end
end
+
+ context 'with CI_DEBUG_TRACE' do
+ let_it_be(:ci_instance_variable) { create(:ci_instance_variable, key: 'CI_DEBUG_TRACE') }
+
+ describe 'trace endpoint' do
+ let_it_be(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
+
+ where(:public_builds, :user_project_role, :ci_debug_trace, :expected_status_code) do
+ true | 'developer' | true | 200
+ true | 'guest' | true | 403
+ true | 'developer' | false | 200
+ true | 'guest' | false | 200
+ false | 'developer' | true | 200
+ false | 'guest' | true | 403
+ false | 'developer' | false | 200
+ false | 'guest' | false | 403
+ end
+
+ with_them do
+ before do
+ ci_instance_variable.update!(value: ci_debug_trace)
+ project.update!(public_builds: public_builds)
+ project.add_role(user, user_project_role)
+ end
+
+ it 'renders trace to authorized users' do
+ visit trace_project_job_path(project, job)
+
+ expect(status_code).to eq(expected_status_code)
+ end
+ end
+
+ context 'when restrict_access_to_build_debug_mode feature not enabled' do
+ where(:public_builds, :user_project_role, :ci_debug_trace, :expected_status_code) do
+ true | 'developer' | true | 200
+ true | 'guest' | true | 200
+ true | 'developer' | false | 200
+ true | 'guest' | false | 200
+ false | 'developer' | true | 200
+ false | 'guest' | true | 403
+ false | 'developer' | false | 200
+ false | 'guest' | false | 403
+ end
+
+ with_them do
+ before do
+ stub_feature_flags(restrict_access_to_build_debug_mode: false)
+ ci_instance_variable.update!(value: ci_debug_trace)
+ project.update!(public_builds: public_builds)
+ project.add_role(user, user_project_role)
+ end
+
+ it 'renders trace to authorized users' do
+ visit trace_project_job_path(project, job)
+
+ expect(status_code).to eq(expected_status_code)
+ end
+ end
+ end
+ end
+
+ describe 'raw page' do
+ let_it_be(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) }
+
+ where(:public_builds, :user_project_role, :ci_debug_trace, :expected_status_code, :expected_msg) do
+ true | 'developer' | true | 200 | nil
+ true | 'guest' | true | 403 | 'You must have developer or higher permissions'
+ true | 'developer' | false | 200 | nil
+ true | 'guest' | false | 200 | nil
+ false | 'developer' | true | 200 | nil
+ false | 'guest' | true | 403 | 'You must have developer or higher permissions'
+ false | 'developer' | false | 200 | nil
+ false | 'guest' | false | 403 | 'The current user is not authorized to access the job log'
+ end
+
+ with_them do
+ before do
+ ci_instance_variable.update!(value: ci_debug_trace)
+ project.update!(public_builds: public_builds)
+ project.add_role(user, user_project_role)
+ end
+
+ it 'renders raw trace to authorized users' do
+ visit raw_project_job_path(project, job)
+
+ expect(status_code).to eq(expected_status_code)
+ expect(page).to have_content(expected_msg)
+ end
+ end
+
+ context 'when restrict_access_to_build_debug_mode feature not enabled' do
+ where(:public_builds, :user_project_role, :ci_debug_trace, :expected_status_code, :expected_msg) do
+ true | 'developer' | true | 200 | nil
+ true | 'guest' | true | 200 | nil
+ true | 'developer' | false | 200 | nil
+ true | 'guest' | false | 200 | nil
+ false | 'developer' | true | 200 | nil
+ false | 'guest' | true | 403 | 'The current user is not authorized to access the job log'
+ false | 'developer' | false | 200 | nil
+ false | 'guest' | false | 403 | 'The current user is not authorized to access the job log'
+ end
+
+ with_them do
+ before do
+ stub_feature_flags(restrict_access_to_build_debug_mode: false)
+ ci_instance_variable.update!(value: ci_debug_trace)
+ project.update!(public_builds: public_builds)
+ project.add_role(user, user_project_role)
+ end
+
+ it 'renders raw trace to authorized users' do
+ visit raw_project_job_path(project, job)
+
+ expect(status_code).to eq(expected_status_code)
+ expect(page).to have_content(expected_msg)
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index e19337e1ff5..4edda9febbe 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -25,72 +25,113 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do
end
describe "GET /:project/jobs" do
- let!(:job) { create(:ci_build, pipeline: pipeline) }
-
- context "Pending scope" do
+ context 'with no jobs' do
before do
- visit project_jobs_path(project, scope: :pending)
- end
+ stub_experiment(jobs_empty_state: experiment_active)
+ stub_experiment_for_subject(jobs_empty_state: in_experiment_group)
- it "shows Pending tab jobs" do
- expect(page).to have_selector('.nav-links li.active', text: 'Pending')
- expect(page).to have_content job.short_sha
- expect(page).to have_content job.ref
- expect(page).to have_content job.name
+ visit project_jobs_path(project)
end
- end
- context "Running scope" do
- before do
- job.run!
- visit project_jobs_path(project, scope: :running)
- end
+ context 'when experiment not active' do
+ let(:experiment_active) { false }
+ let(:in_experiment_group) { false }
- it "shows Running tab jobs" do
- expect(page).to have_selector('.nav-links li.active', text: 'Running')
- expect(page).to have_content job.short_sha
- expect(page).to have_content job.ref
- expect(page).to have_content job.name
+ it 'shows the empty state control page' do
+ expect(page).to have_content('No jobs to show')
+ expect(page).to have_link('Get started with Pipelines')
+ end
end
- end
- context "Finished scope" do
- before do
- job.run!
- visit project_jobs_path(project, scope: :finished)
+ context 'when experiment active and user in control group' do
+ let(:experiment_active) { true }
+ let(:in_experiment_group) { false }
+
+ it 'shows the empty state control page' do
+ expect(page).to have_content('No jobs to show')
+ expect(page).to have_link('Get started with Pipelines')
+ end
end
- it "shows Finished tab jobs" do
- expect(page).to have_selector('.nav-links li.active', text: 'Finished')
- expect(page).to have_content 'No jobs to show'
+ context 'when experiment active and user in experimental group' do
+ let(:experiment_active) { true }
+ let(:in_experiment_group) { true }
+
+ it 'shows the empty state experiment page' do
+ expect(page).to have_content('Use jobs to automate your tasks')
+ expect(page).to have_link('Create CI/CD configuration file')
+ end
end
end
- context "All jobs" do
- before do
- project.builds.running_or_pending.each(&:success)
- visit project_jobs_path(project)
+ context 'with a job' do
+ let!(:job) { create(:ci_build, pipeline: pipeline) }
+
+ context "Pending scope" do
+ before do
+ visit project_jobs_path(project, scope: :pending)
+ end
+
+ it "shows Pending tab jobs" do
+ expect(page).to have_selector('.nav-links li.active', text: 'Pending')
+ expect(page).to have_content job.short_sha
+ expect(page).to have_content job.ref
+ expect(page).to have_content job.name
+ end
end
- it "shows All tab jobs" do
- expect(page).to have_selector('.nav-links li.active', text: 'All')
- expect(page).to have_content job.short_sha
- expect(page).to have_content job.ref
- expect(page).to have_content job.name
+ context "Running scope" do
+ before do
+ job.run!
+ visit project_jobs_path(project, scope: :running)
+ end
+
+ it "shows Running tab jobs" do
+ expect(page).to have_selector('.nav-links li.active', text: 'Running')
+ expect(page).to have_content job.short_sha
+ expect(page).to have_content job.ref
+ expect(page).to have_content job.name
+ end
end
- end
- context "when visiting old URL" do
- let(:jobs_url) do
- project_jobs_path(project)
+ context "Finished scope" do
+ before do
+ job.run!
+ visit project_jobs_path(project, scope: :finished)
+ end
+
+ it "shows Finished tab jobs" do
+ expect(page).to have_selector('.nav-links li.active', text: 'Finished')
+ expect(page).to have_content 'No jobs to show'
+ end
end
- before do
- visit jobs_url.sub('/-/jobs', '/builds')
+ context "All jobs" do
+ before do
+ project.builds.running_or_pending.each(&:success)
+ visit project_jobs_path(project)
+ end
+
+ it "shows All tab jobs" do
+ expect(page).to have_selector('.nav-links li.active', text: 'All')
+ expect(page).to have_content job.short_sha
+ expect(page).to have_content job.ref
+ expect(page).to have_content job.name
+ end
end
- it "redirects to new URL" do
- expect(page.current_path).to eq(jobs_url)
+ context "when visiting old URL" do
+ let(:jobs_url) do
+ project_jobs_path(project)
+ end
+
+ before do
+ visit jobs_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(jobs_url)
+ end
end
end
end
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 85a08c441ca..0a373b0d51a 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe 'Issue prioritization' do
# According to https://gitlab.com/gitlab-org/gitlab-foss/issues/14189#note_4360653
context 'when issues have one label', :js do
- it 'Are sorted properly' do
+ it 'are sorted properly' do
# Issues
issue_1 = create(:issue, title: 'issue_1', project: project)
issue_2 = create(:issue, title: 'issue_2', project: project)
@@ -45,7 +45,7 @@ RSpec.describe 'Issue prioritization' do
end
context 'when issues have multiple labels', :js do
- it 'Are sorted properly' do
+ it 'are sorted properly' do
# Issues
issue_1 = create(:issue, title: 'issue_1', project: project)
issue_2 = create(:issue, title: 'issue_2', project: project)
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 6a2ec9aa4a8..4aabf040655 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'New project' do
+RSpec.describe 'New project', :js do
include Select2Helper
context 'as a user' do
@@ -18,6 +18,7 @@ RSpec.describe 'New project' do
)
visit new_project_path
+ find('[data-qa-selector="blank_project_link"]').click
expect(page).to have_content 'Other visibility settings have been disabled by the administrator.'
end
@@ -28,6 +29,7 @@ RSpec.describe 'New project' do
)
visit new_project_path
+ find('[data-qa-selector="blank_project_link"]').click
expect(page).to have_content 'Visibility settings have been disabled by the administrator.'
end
@@ -42,17 +44,18 @@ RSpec.describe 'New project' do
it 'shows "New project" page', :js do
visit new_project_path
+ find('[data-qa-selector="blank_project_link"]').click
expect(page).to have_content('Project name')
expect(page).to have_content('Project URL')
expect(page).to have_content('Project slug')
- find('#import-project-tab').click
+ click_link('New project')
+ find('[data-qa-selector="import_project_link"]').click
expect(page).to have_link('GitHub')
expect(page).to have_link('Bitbucket')
expect(page).to have_link('GitLab.com')
- expect(page).to have_link('Google Code')
expect(page).to have_button('Repo by URL')
expect(page).to have_link('GitLab export')
end
@@ -61,7 +64,7 @@ RSpec.describe 'New project' do
before do
visit new_project_path
- find('#import-project-tab').click
+ find('[data-qa-selector="import_project_link"]').click
end
it { expect(page).to have_link('Manifest file') }
@@ -73,6 +76,7 @@ RSpec.describe 'New project' do
stub_application_setting(default_project_visibility: level)
visit new_project_path
+ find('[data-qa-selector="blank_project_link"]').click
page.within('#blank-project-pane') do
expect(find_field("project_visibility_level_#{level}")).to be_checked
end
@@ -80,6 +84,7 @@ RSpec.describe 'New project' do
it "saves visibility level #{level} on validation error" do
visit new_project_path
+ find('[data-qa-selector="blank_project_link"]').click
choose(s_(key))
click_button('Create project')
@@ -97,6 +102,7 @@ RSpec.describe 'New project' do
it 'has private selected' do
group = create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
visit new_project_path(namespace_id: group.id)
+ find('[data-qa-selector="blank_project_link"]').click
page.within('#blank-project-pane') do
expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
@@ -112,6 +118,7 @@ RSpec.describe 'New project' do
it 'has private selected' do
group = create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
+ find('[data-qa-selector="blank_project_link"]').click
page.within('#blank-project-pane') do
expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
@@ -123,6 +130,7 @@ RSpec.describe 'New project' do
context 'Readme selector' do
it 'shows the initialize with Readme checkbox on "Blank project" tab' do
visit new_project_path
+ find('[data-qa-selector="blank_project_link"]').click
expect(page).to have_css('input#project_initialize_with_readme')
expect(page).to have_content('Initialize repository with a README')
@@ -130,7 +138,7 @@ RSpec.describe 'New project' do
it 'does not show the initialize with Readme checkbox on "Create from template" tab' do
visit new_project_path
- find('#create-from-template-pane').click
+ find('[data-qa-selector="create_from_template_link"]').click
first('.choose-template').click
page.within '.project-fields-form' do
@@ -141,7 +149,7 @@ RSpec.describe 'New project' do
it 'does not show the initialize with Readme checkbox on "Import project" tab' do
visit new_project_path
- find('#import-project-tab').click
+ find('[data-qa-selector="import_project_link"]').click
first('.js-import-git-toggle-button').click
page.within '.toggle-import-form' do
@@ -155,13 +163,12 @@ RSpec.describe 'New project' do
context 'with user namespace' do
before do
visit new_project_path
+ find('[data-qa-selector="blank_project_link"]').click
end
it 'selects the user namespace' do
page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id')
-
- expect(namespace.text).to eq user.username
+ expect(page).to have_select('project[namespace_id]', visible: false, selected: user.username)
end
end
end
@@ -172,13 +179,12 @@ RSpec.describe 'New project' do
before do
group.add_owner(user)
visit new_project_path(namespace_id: group.id)
+ find('[data-qa-selector="blank_project_link"]').click
end
it 'selects the group namespace' do
page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id option[selected]')
-
- expect(namespace.text).to eq group.name
+ expect(page).to have_select('project[namespace_id]', visible: false, selected: group.name)
end
end
end
@@ -190,13 +196,12 @@ RSpec.describe 'New project' do
before do
group.add_maintainer(user)
visit new_project_path(namespace_id: subgroup.id)
+ find('[data-qa-selector="blank_project_link"]').click
end
it 'selects the group namespace' do
page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id option[selected]')
-
- expect(namespace.text).to eq subgroup.full_path
+ expect(page).to have_select('project[namespace_id]', visible: false, selected: subgroup.full_path)
end
end
end
@@ -211,6 +216,7 @@ RSpec.describe 'New project' do
internal_group.add_owner(user)
private_group.add_owner(user)
visit new_project_path(namespace_id: public_group.id)
+ find('[data-qa-selector="blank_project_link"]').click
end
it 'enables the correct visibility options' do
@@ -240,7 +246,7 @@ RSpec.describe 'New project' do
context 'Import project options', :js do
before do
visit new_project_path
- find('#import-project-tab').click
+ find('[data-qa-selector="import_project_link"]').click
end
context 'from git repository url, "Repo by URL"' do
@@ -285,17 +291,6 @@ RSpec.describe 'New project' do
end
end
- context 'from Google Code' do
- before do
- first('.import_google_code').click
- end
-
- it 'shows import instructions' do
- expect(page).to have_content('Import projects from Google Code')
- expect(current_path).to eq new_import_google_code_path
- end
- end
-
context 'from manifest file' do
before do
first('.import_manifest').click
@@ -315,13 +310,12 @@ RSpec.describe 'New project' do
before do
group.add_developer(user)
visit new_project_path(namespace_id: group.id)
+ find('[data-qa-selector="blank_project_link"]').click
end
it 'selects the group namespace' do
page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id option[selected]')
-
- expect(namespace.text).to eq group.full_path
+ expect(page).to have_select('project[namespace_id]', visible: false, selected: group.full_path)
end
end
end
diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb
index de7251db5c9..1d9f256a819 100644
--- a/spec/features/projects/settings/operations_settings_spec.rb
+++ b/spec/features/projects/settings/operations_settings_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
describe 'Settings > Operations' do
describe 'Incidents' do
- let(:create_issue) { 'Create an issue. Issues are created for each alert triggered.' }
+ let(:create_issue) { 'Create an incident. Incidents are created for each alert triggered.' }
let(:send_email) { 'Send a separate email notification to Developers.' }
before do
diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb
index cb333bdb428..2b03ecf5af1 100644
--- a/spec/features/projects/settings/registry_settings_spec.rb
+++ b/spec/features/projects/settings/registry_settings_spec.rb
@@ -26,20 +26,20 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
subject
settings_block = find('#js-registry-policies')
- expect(settings_block).to have_text 'Cleanup policy for tags'
+ expect(settings_block).to have_text 'Clean up image tags'
end
it 'saves cleanup policy submit the form' do
subject
within '#js-registry-policies' do
- within '.gl-card-body' do
- select('7 days until tags are automatically removed', from: 'Expiration interval:')
- select('Every day', from: 'Expiration schedule:')
- select('50 tags per image name', from: 'Number of tags to retain:')
- fill_in('Tags with names matching this regex pattern will expire:', with: '.*-production')
- end
- submit_button = find('.gl-card-footer .btn.btn-success')
+ select('Every day', from: 'Run cleanup')
+ select('50 tags per image name', from: 'Keep the most recent:')
+ fill_in('Keep tags matching:', with: 'stable')
+ select('7 days', from: 'Remove tags older than:')
+ fill_in('Remove tags matching:', with: '.*-production')
+
+ submit_button = find('.btn.btn-success')
expect(submit_button).not_to be_disabled
submit_button.click
end
@@ -51,10 +51,9 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
subject
within '#js-registry-policies' do
- within '.gl-card-body' do
- fill_in('Tags with names matching this regex pattern will expire:', with: '*-production')
- end
- submit_button = find('.gl-card-footer .btn.btn-success')
+ fill_in('Remove tags matching:', with: '*-production')
+
+ submit_button = find('.btn.btn-success')
expect(submit_button).not_to be_disabled
submit_button.click
end
@@ -85,7 +84,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
within '#js-registry-policies' do
case result
when :available_section
- expect(find('.gl-card-header')).to have_content('Tag expiration policy')
+ expect(find('[data-testid="enable-toggle"]')).to have_content('Disabled - Tags will not be automatically deleted.')
when :disabled_message
expect(find('.gl-alert-title')).to have_content('Cleanup policy for tags is disabled')
end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 8c7b7bc70a2..3e520142117 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -289,13 +289,13 @@ RSpec.describe 'Projects > Settings > Repository settings' do
visit project_settings_repository_path(project)
end
- context 'when project mirroring is enabled' do
+ context 'when project mirroring is enabled', :enable_admin_mode do
let(:mirror_available) { true }
include_examples 'shows mirror settings'
end
- context 'when project mirroring is disabled' do
+ context 'when project mirroring is disabled', :enable_admin_mode do
let(:mirror_available) { false }
include_examples 'shows mirror settings'
diff --git a/spec/features/projects/settings/service_desk_setting_spec.rb b/spec/features/projects/settings/service_desk_setting_spec.rb
index 59e6f54da2f..d31913d2dcf 100644
--- a/spec/features/projects/settings/service_desk_setting_spec.rb
+++ b/spec/features/projects/settings/service_desk_setting_spec.rb
@@ -14,20 +14,57 @@ RSpec.describe 'Service Desk Setting', :js do
allow_any_instance_of(Project).to receive(:present).with(current_user: user).and_return(presenter)
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
-
- visit edit_project_path(project)
end
it 'shows activation checkbox' do
+ visit edit_project_path(project)
+
expect(page).to have_selector("#service-desk-checkbox")
end
- it 'shows incoming email after activating' do
- find("#service-desk-checkbox").click
- wait_for_requests
- project.reload
- expect(project.service_desk_enabled).to be_truthy
- expect(project.service_desk_address).to be_present
- expect(find('[data-testid="incoming-email"]').value).to eq(project.service_desk_address)
+ context 'when service_desk_email is disabled' do
+ before do
+ allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(false)
+
+ visit edit_project_path(project)
+ end
+
+ it 'shows incoming email but not project name suffix after activating' do
+ find("#service-desk-checkbox").click
+
+ wait_for_requests
+
+ project.reload
+ expect(project.service_desk_enabled).to be_truthy
+ expect(project.service_desk_address).to be_present
+ expect(find('[data-testid="incoming-email"]').value).to eq(project.service_desk_incoming_address)
+ expect(page).not_to have_selector('#service-desk-project-suffix')
+ end
+ end
+
+ context 'when service_desk_email is enabled' do
+ before do
+ allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?) { true }
+ allow(::Gitlab::ServiceDeskEmail).to receive(:address_for_key) { 'address-suffix@example.com' }
+
+ visit edit_project_path(project)
+ end
+
+ it 'allows setting of custom address suffix' do
+ find("#service-desk-checkbox").click
+ wait_for_requests
+
+ project.reload
+ expect(find('[data-testid="incoming-email"]').value).to eq(project.service_desk_incoming_address)
+
+ page.within '#js-service-desk' do
+ fill_in('service-desk-project-suffix', with: 'foo')
+ click_button 'Save changes'
+ end
+
+ wait_for_requests
+
+ expect(find('[data-testid="incoming-email"]').value).to eq('address-suffix@example.com')
+ end
end
end
diff --git a/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb b/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb
index ba504624823..91a7753fe6d 100644
--- a/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb
+++ b/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe "User interacts with deploy keys", :js do
click_button("Enable")
- expect(page).not_to have_selector(".fa-spinner")
+ expect(page).not_to have_selector(".gl-spinner")
expect(current_path).to eq(project_settings_repository_path(project))
find(".js-deployKeys-tab-enabled_keys").click
diff --git a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
deleted file mode 100644
index 8d239cb2cbf..00000000000
--- a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Projects > Show > Developer views empty project instructions' do
- let(:project) { create(:project, :empty_repo) }
- let(:developer) { create(:user) }
-
- before do
- project.add_developer(developer)
-
- sign_in(developer)
- end
-
- it 'displays "git clone" instructions' do
- visit project_path(project)
-
- expect(page).to have_content("git clone")
- end
-end
diff --git a/spec/features/projects/show/no_password_spec.rb b/spec/features/projects/show/no_password_spec.rb
index 79cd65e5406..d18ff75b324 100644
--- a/spec/features/projects/show/no_password_spec.rb
+++ b/spec/features/projects/show/no_password_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe 'No Password Alert' do
let(:user) { create(:user) }
it 'shows no alert' do
- expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account"
+ expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you set a password on your account"
end
end
@@ -23,7 +23,7 @@ RSpec.describe 'No Password Alert' do
let(:user) { create(:user, password_automatically_set: true) }
it 'shows a password alert' do
- expect(page).to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account"
+ expect(page).to have_content "You won't be able to pull or push repositories via HTTP until you set a password on your account"
end
end
end
@@ -41,7 +41,7 @@ RSpec.describe 'No Password Alert' do
gitlab_sign_in_via('saml', user, 'my-uid')
visit project_path(project)
- expect(page).to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
+ expect(page).to have_content "You won't be able to pull or push repositories via HTTP until you create a personal access token on your account"
end
end
@@ -51,7 +51,7 @@ RSpec.describe 'No Password Alert' do
gitlab_sign_in_via('saml', user, 'my-uid')
visit project_path(project)
- expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
+ expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you create a personal access token on your account"
end
end
end
@@ -65,7 +65,7 @@ RSpec.describe 'No Password Alert' do
end
it 'shows no alert' do
- expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you"
+ expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you"
end
end
end
diff --git a/spec/features/projects/show/schema_markup_spec.rb b/spec/features/projects/show/schema_markup_spec.rb
index e651798af23..1777b72cbf5 100644
--- a/spec/features/projects/show/schema_markup_spec.rb
+++ b/spec/features/projects/show/schema_markup_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'Projects > Show > Schema Markup' do
expect(page).to have_selector('img[itemprop="image"]')
expect(page).to have_selector('[itemprop="name"]', text: project.name)
expect(page).to have_selector('[itemprop="identifier"]', text: "Project ID: #{project.id}")
- expect(page).to have_selector('[itemprop="abstract"]', text: project.description)
+ expect(page).to have_selector('[itemprop="description"]', text: project.description)
expect(page).to have_selector('[itemprop="license"]', text: project.repository.license.name)
expect(find_all('[itemprop="keywords"]').map(&:text)).to match_array(project.tag_list.map(&:capitalize))
expect(page).to have_selector('[itemprop="about"]')
diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb
index 3b77fd7eebf..febdb70de86 100644
--- a/spec/features/projects/show/user_sees_git_instructions_spec.rb
+++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb
@@ -122,13 +122,11 @@ RSpec.describe 'Projects > Show > User sees Git instructions' do
context 'when project is not empty' do
let_it_be(:project) { create(:project, :public, :repository) }
- before do
- visit(project_path(project))
- end
-
context 'when not signed in' do
before do
allow(Gitlab.config.gitlab).to receive(:host).and_return('www.example.com')
+
+ visit(project_path(project))
end
include_examples 'shows details of non empty project'
diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
index 189aa45ff75..9b51e867156 100644
--- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
+++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
visit project_path(project)
end
- it 'Project buttons are not visible' do
+ it 'project buttons are not visible' do
visit project_path(project)
page.within('.project-buttons') do
@@ -165,7 +165,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
context 'when the project does not have a README' do
it 'shows the single file editor "Add README" button' do
- allow(project.repository).to receive(:readme).and_return(nil)
+ allow(project.repository).to receive(:readme_path).and_return(nil)
visit project_path(project)
diff --git a/spec/features/projects/terraform_spec.rb b/spec/features/projects/terraform_spec.rb
index 2680dfb2b13..dfa4dad8490 100644
--- a/spec/features/projects/terraform_spec.rb
+++ b/spec/features/projects/terraform_spec.rb
@@ -4,44 +4,94 @@ require 'spec_helper'
RSpec.describe 'Terraform', :js do
let_it_be(:project) { create(:project) }
+ let_it_be(:terraform_state) { create(:terraform_state, :locked, :with_version, project: project) }
- let(:user) { project.creator }
+ context 'when user is a terraform administrator' do
+ let(:admin) { project.creator }
- before do
- gitlab_sign_in(user)
- end
-
- context 'when user does not have any terraform states and visits index page' do
before do
- visit project_terraform_index_path(project)
+ gitlab_sign_in(admin)
end
- it 'sees an empty state' do
- expect(page).to have_content('Get started with Terraform')
+ context 'when user does not have any terraform states and visits the index page' do
+ let(:empty_project) { create(:project) }
+
+ before do
+ empty_project.add_maintainer(admin)
+ visit project_terraform_index_path(empty_project)
+ end
+
+ it 'sees an empty state' do
+ expect(page).to have_content('Get started with Terraform')
+ end
end
- end
- context 'when user has a terraform state' do
- let_it_be(:terraform_state) { create(:terraform_state, :locked, project: project) }
+ context 'when user has a terraform state' do
+ context 'when user visits the index page' do
+ before do
+ visit project_terraform_index_path(project)
+ end
- context 'when user visits the index page' do
- before do
- visit project_terraform_index_path(project)
+ it 'displays a tab with states count' do
+ expect(page).to have_content("States #{project.terraform_states.size}")
+ end
+
+ it 'displays a table with terraform states' do
+ expect(page).to have_selector(
+ '[data-testid="terraform-states-table-name"]',
+ count: project.terraform_states.size
+ )
+ end
+
+ it 'displays terraform actions dropdown' do
+ expect(page).to have_selector(
+ '[data-testid*="terraform-state-actions"]',
+ count: project.terraform_states.size
+ )
+ end
+
+ it 'displays terraform information' do
+ expect(page).to have_content(terraform_state.name)
+ end
end
- it 'displays a tab with states count' do
- expect(page).to have_content("States #{project.terraform_states.size}")
+ context 'when clicking on the delete button' do
+ let(:additional_state) { create(:terraform_state, project: project) }
+
+ it 'removes the state', :aggregate_failures do
+ visit project_terraform_index_path(project)
+
+ expect(page).to have_content(additional_state.name)
+
+ find("[data-testid='terraform-state-actions-#{additional_state.name}']").click
+ find('[data-testid="terraform-state-remove"]').click
+ fill_in "terraform-state-remove-input-#{additional_state.name}", with: additional_state.name
+ click_button 'Remove'
+
+ expect(page).not_to have_content(additional_state.name)
+ expect { additional_state.reload }.to raise_error ActiveRecord::RecordNotFound
+ end
end
+ end
+ end
+
+ context 'when user is a terraform developer' do
+ let_it_be(:developer) { create(:user) }
- it 'displays a table with terraform states' do
+ before do
+ project.add_developer(developer)
+ gitlab_sign_in(developer)
+ visit project_terraform_index_path(project)
+ end
+
+ context 'when user visits the index page' do
+ it 'displays a table without an action dropdown', :aggregate_failures do
expect(page).to have_selector(
- '[data-testid="terraform-states-table"] tbody tr',
+ '[data-testid="terraform-states-table-name"]',
count: project.terraform_states.size
)
- end
- it 'displays terraform information' do
- expect(page).to have_content(terraform_state.name)
+ expect(page).not_to have_selector('[data-testid*="terraform-state-actions"]')
end
end
end
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
index b204ae76e07..feb5f348256 100644
--- a/spec/features/projects/user_creates_project_spec.rb
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe 'User creates a project', :js do
it 'creates a new project' do
visit(new_project_path)
+ find('[data-qa-selector="blank_project_link"]').click
fill_in(:project_name, with: 'Empty')
page.within('#content-body') do
@@ -39,6 +40,7 @@ RSpec.describe 'User creates a project', :js do
it 'creates a new project' do
visit(new_project_path)
+ find('[data-qa-selector="blank_project_link"]').click
fill_in :project_name, with: 'A Subgroup Project'
fill_in :project_path, with: 'a-subgroup-project'
@@ -67,6 +69,7 @@ RSpec.describe 'User creates a project', :js do
it 'creates a new project' do
visit(new_project_path)
+ find('[data-qa-selector="blank_project_link"]').click
fill_in :project_name, with: 'a-new-project'
fill_in :project_path, with: 'a-new-project'
diff --git a/spec/features/projects/user_sorts_projects_spec.rb b/spec/features/projects/user_sorts_projects_spec.rb
new file mode 100644
index 00000000000..6a5ed49f1a6
--- /dev/null
+++ b/spec/features/projects/user_sorts_projects_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'User sorts projects and order persists' do
+ include CookieHelper
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:group_member) { create(:group_member, :maintainer, user: user, group: group) }
+ let_it_be(:project) { create(:project, :public, group: group) }
+
+ shared_examples_for "sort order persists across all views" do |project_paths_label, group_paths_label|
+ it "is set on the dashboard_projects_path" do
+ visit(dashboard_projects_path)
+
+ expect(find('.dropdown-menu a.is-active', text: project_paths_label)).to have_content(project_paths_label)
+ end
+
+ it "is set on the explore_projects_path" do
+ visit(explore_projects_path)
+
+ expect(find('.dropdown-menu a.is-active', text: project_paths_label)).to have_content(project_paths_label)
+ end
+
+ it "is set on the group_canonical_path" do
+ visit(group_canonical_path(group))
+
+ expect(find('.dropdown-menu a.is-active', text: group_paths_label)).to have_content(group_paths_label)
+ end
+
+ it "is set on the details_group_path" do
+ visit(details_group_path(group))
+
+ expect(find('.dropdown-menu a.is-active', text: group_paths_label)).to have_content(group_paths_label)
+ end
+ end
+
+ context "from explore projects" do
+ before do
+ sign_in(user)
+ visit(explore_projects_path)
+ find('#sort-projects-dropdown').click
+ first(:link, 'Last updated').click
+ end
+
+ it_behaves_like "sort order persists across all views", "Last updated", "Last updated"
+ end
+
+ context 'from dashboard projects' do
+ before do
+ sign_in(user)
+ visit(dashboard_projects_path)
+ find('#sort-projects-dropdown').click
+ first(:link, 'Name').click
+ end
+
+ it_behaves_like "sort order persists across all views", "Name", "Name"
+ end
+
+ context 'from group homepage' do
+ before do
+ sign_in(user)
+ visit(group_canonical_path(group))
+ find('button.dropdown-menu-toggle').click
+ first(:link, 'Last created').click
+ end
+
+ it_behaves_like "sort order persists across all views", "Created date", "Last created"
+ end
+
+ context 'from group details' do
+ before do
+ sign_in(user)
+ visit(details_group_path(group))
+ find('button.dropdown-menu-toggle').click
+ first(:link, 'Most stars').click
+ end
+
+ it_behaves_like "sort order persists across all views", "Stars", "Most stars"
+ end
+end
diff --git a/spec/features/projects/user_views_empty_project_spec.rb b/spec/features/projects/user_views_empty_project_spec.rb
index 9202d18b86f..3d4d9a7ea96 100644
--- a/spec/features/projects/user_views_empty_project_spec.rb
+++ b/spec/features/projects/user_views_empty_project_spec.rb
@@ -7,12 +7,9 @@ RSpec.describe 'User views an empty project' do
let(:user) { create(:user) }
shared_examples 'allowing push to default branch' do
- before do
- sign_in(user)
+ it 'shows push-to-master instructions' do
visit project_path(project)
- end
- it 'shows push-to-master instructions' do
expect(page).to have_content('git push -u origin master')
end
end
@@ -20,6 +17,7 @@ RSpec.describe 'User views an empty project' do
describe 'as a maintainer' do
before do
project.add_maintainer(user)
+ sign_in(user)
end
it_behaves_like 'allowing push to default branch'
@@ -28,17 +26,33 @@ RSpec.describe 'User views an empty project' do
describe 'as an admin' do
let(:user) { create(:user, :admin) }
- it_behaves_like 'allowing push to default branch'
+ context 'when admin mode is enabled' do
+ before do
+ sign_in(user)
+ gitlab_enable_admin_mode_sign_in(user)
+ end
+
+ it_behaves_like 'allowing push to default branch'
+ end
+
+ context 'when admin mode is disabled' do
+ it 'does not show push-to-master instructions' do
+ visit project_path(project)
+
+ expect(page).not_to have_content('git push -u origin master')
+ end
+ end
end
describe 'as a developer' do
before do
project.add_developer(user)
sign_in(user)
- visit project_path(project)
end
it 'does not show push-to-master instructions' do
+ visit project_path(project)
+
expect(page).not_to have_content('git push -u origin master')
end
end
diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
deleted file mode 100644
index 83679c6bd1d..00000000000
--- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Projects > Wiki > User views Git access wiki page' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :wiki_repo, :public) }
- let(:wiki_page) { create(:wiki_page, wiki: project.wiki, title: 'home', content: '[some link](other-page)') }
-
- before do
- sign_in(user)
- end
-
- it 'Visit Wiki Page Current Commit' do
- visit project_wiki_path(project, wiki_page)
-
- click_link 'Clone repository'
- expect(page).to have_text("Clone repository #{project.wiki.full_path}")
- expect(page).to have_text(project.wiki.http_url_to_repo)
- end
-end
diff --git a/spec/features/projects/wikis_spec.rb b/spec/features/projects/wikis_spec.rb
index 1c66ad81145..621f8c71b20 100644
--- a/spec/features/projects/wikis_spec.rb
+++ b/spec/features/projects/wikis_spec.rb
@@ -17,4 +17,5 @@ RSpec.describe 'Project wikis' do
it_behaves_like 'User views a wiki page'
it_behaves_like 'User views wiki pages'
it_behaves_like 'User views wiki sidebar'
+ it_behaves_like 'User views Git access wiki page'
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 9b5f4ca6d48..618a256d4fb 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'Project' do
shared_examples 'creates from template' do |template, sub_template_tab = nil|
it "is created from template", :js do
- find('#create-from-template-tab').click
+ find('[data-qa-selector="create_from_template_link"]').click
find(".project-template #{sub_template_tab}").click if sub_template_tab
find("label[for=#{template.name}]").click
fill_in("project_name", with: template.name)
@@ -34,7 +34,7 @@ RSpec.describe 'Project' do
end
context 'create with sample data template' do
- it_behaves_like 'creates from template', Gitlab::SampleDataTemplate.find(:basic), '.sample-data-templates-tab'
+ it_behaves_like 'creates from template', Gitlab::SampleDataTemplate.find(:sample)
end
end
@@ -47,9 +47,7 @@ RSpec.describe 'Project' do
end
it 'shows the command in a popover', :js do
- page.within '.profile-settings-sidebar' do
- click_link 'Show command'
- end
+ click_link 'Show command'
expect(page).to have_css('.popover .push-to-create-popover #push_to_create_tip')
expect(page).to have_content 'Private projects can be created in your personal namespace with:'
@@ -61,7 +59,7 @@ RSpec.describe 'Project' do
let(:path) { project_path(project) }
before do
- sign_in(create(:admin))
+ sign_in(project.owner)
end
it 'parses Markdown' do
@@ -125,7 +123,7 @@ RSpec.describe 'Project' do
let(:path) { project_path(project) }
before do
- sign_in(create(:admin))
+ sign_in(project.owner)
visit path
end
@@ -156,7 +154,7 @@ RSpec.describe 'Project' do
let(:path) { project_path(project) }
before do
- sign_in(create(:admin))
+ sign_in(project.owner)
visit path
end
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 3be01595502..95d268ab1be 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -73,6 +73,7 @@ RSpec.describe 'Protected Branches', :js do
context 'logged in as admin' do
before do
sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
describe "explicit protected branches" do
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index 12e4bbde293..25447db3c8d 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe 'Protected Tags', :js do
include ProtectedTagHelpers
- let(:user) { create(:user, :admin) }
let(:project) { create(:project, :repository) }
+ let(:user) { project.owner }
before do
sign_in(user)
diff --git a/spec/features/registrations/experience_level_spec.rb b/spec/features/registrations/experience_level_spec.rb
index 06d380926cd..25496e2fef1 100644
--- a/spec/features/registrations/experience_level_spec.rb
+++ b/spec/features/registrations/experience_level_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe 'Experience level screen' do
before do
group.add_owner(user)
gitlab_sign_in(user)
- stub_experiment_for_user(onboarding_issues: true)
+ stub_experiment_for_subject(onboarding_issues: true)
visit users_sign_up_experience_level_path(namespace_path: group.to_param)
end
@@ -23,14 +23,14 @@ RSpec.describe 'Experience level screen' do
it 'shows the option for novice' do
is_expected.to have_content('Novice')
- is_expected.to have_content('I’m not very familiar with the basics of project management and DevOps')
- is_expected.to have_content('Show me everything')
+ is_expected.to have_content('I’m not familiar with the basics of DevOps')
+ is_expected.to have_content('Show me the basics')
end
it 'shows the option for experienced' do
is_expected.to have_content('Experienced')
- is_expected.to have_content('I’m familiar with the basics of project management and DevOps')
- is_expected.to have_content('Show me more advanced stuff')
+ is_expected.to have_content('I’m familiar with the basics of DevOps')
+ is_expected.to have_content('Show me advanced features')
end
it 'does not display any flash messages' do
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index 9697e10c3d1..5cddad81927 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -179,16 +179,32 @@ RSpec.describe 'Runners' do
context 'when a project has disabled shared_runners' do
let(:project) { create(:project, shared_runners_enabled: false) }
- before do
- project.add_maintainer(user)
+ context 'when feature flag: vueify_shared_runners_toggle is disabled' do
+ before do
+ stub_feature_flags(vueify_shared_runners_toggle: false)
+ project.add_maintainer(user)
+ end
+
+ it 'user enables shared runners' do
+ visit project_runners_path(project)
+
+ click_on 'Enable shared runners'
+
+ expect(page.find('.shared-runners-description')).to have_content('Disable shared runners')
+ expect(page).not_to have_selector('#toggle-shared-runners-form')
+ end
end
- it 'user enables shared runners' do
- visit project_runners_path(project)
+ context 'when feature flag: vueify_shared_runners_toggle is enabled' do
+ before do
+ project.add_maintainer(user)
+ end
- click_on 'Enable shared runners'
+ it 'user enables shared runners' do
+ visit project_runners_path(project)
- expect(page.find('.shared-runners-description')).to have_content('Disable shared runners')
+ expect(page).to have_selector('#toggle-shared-runners-form')
+ end
end
end
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index f761bd30baf..ee3717b3e42 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -27,8 +27,13 @@ RSpec.describe 'User searches for code' do
context 'when on a project page', :js do
before do
visit(search_path)
- find('.js-search-project-dropdown').click
- find('[data-testid="project-filter"]').click_link(project.full_name)
+ find('[data-testid="project-filter"]').click
+
+ wait_for_requests
+
+ page.within('[data-testid="project-filter"]') do
+ click_on(project.full_name)
+ end
end
include_examples 'top right search form'
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
index e2ae2738d2f..e253b9f2f7a 100644
--- a/spec/features/search/user_searches_for_issues_spec.rb
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -85,8 +85,13 @@ RSpec.describe 'User searches for issues', :js do
context 'when on a project page' do
it 'finds an issue' do
- find('.js-search-project-dropdown').click
- find('[data-testid="project-filter"]').click_link(project.full_name)
+ find('[data-testid="project-filter"]').click
+
+ wait_for_requests
+
+ page.within('[data-testid="project-filter"]') do
+ click_on(project.full_name)
+ end
search_for_issue(issue1.title)
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
index 6f8f6303b66..21e8075739f 100644
--- a/spec/features/search/user_searches_for_merge_requests_spec.rb
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -30,8 +30,13 @@ RSpec.describe 'User searches for merge requests', :js do
context 'when on a project page' do
it 'finds a merge request' do
- find('.js-search-project-dropdown').click
- find('[data-testid="project-filter"]').click_link(project.full_name)
+ find('[data-testid="project-filter"]').click
+
+ wait_for_requests
+
+ page.within('[data-testid="project-filter"]') do
+ click_on(project.full_name)
+ end
fill_in('dashboard_search', with: merge_request1.title)
find('.btn-search').click
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index 1a2227db214..f4df91dbc08 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -30,8 +30,13 @@ RSpec.describe 'User searches for milestones', :js do
context 'when on a project page' do
it 'finds a milestone' do
- find('.js-search-project-dropdown').click
- find('[data-testid="project-filter"]').click_link(project.full_name)
+ find('[data-testid="project-filter"]').click
+
+ wait_for_requests
+
+ page.within('[data-testid="project-filter"]') do
+ click_on(project.full_name)
+ end
fill_in('dashboard_search', with: milestone1.title)
find('.btn-search').click
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 6bf1407fd4f..72bd1193fc9 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -18,8 +18,13 @@ RSpec.describe 'User searches for wiki pages', :js do
shared_examples 'search wiki blobs' do
it 'finds a page' do
- find('.js-search-project-dropdown').click
- find('[data-testid="project-filter"]').click_link(project.full_name)
+ find('[data-testid="project-filter"]').click
+
+ wait_for_requests
+
+ page.within('[data-testid="project-filter"]') do
+ click_on(project.full_name)
+ end
fill_in('dashboard_search', with: search_term)
find('.btn-search').click
diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb
index bd77e6003e3..86017ca64c5 100644
--- a/spec/features/search/user_uses_search_filters_spec.rb
+++ b/spec/features/search/user_uses_search_filters_spec.rb
@@ -28,13 +28,15 @@ RSpec.describe 'User uses search filters', :js do
expect(find('[data-testid="group-filter"]')).to have_content(group.name)
- page.within('[data-testid="project-filter"]') do
- find('.js-search-project-dropdown').click
+ find('[data-testid="project-filter"]').click
- wait_for_requests
+ wait_for_requests
- expect(page).to have_link(group_project.full_name)
+ page.within('[data-testid="project-filter"]') do
+ click_on(group_project.full_name)
end
+
+ expect(find('[data-testid="project-filter"]')).to have_content(group_project.full_name)
end
context 'when the group filter is set' do
@@ -58,15 +60,15 @@ RSpec.describe 'User uses search filters', :js do
it 'shows a project' do
visit search_path
- page.within('[data-testid="project-filter"]') do
- find('.js-search-project-dropdown').click
+ find('[data-testid="project-filter"]').click
- wait_for_requests
+ wait_for_requests
- click_link(project.full_name)
+ page.within('[data-testid="project-filter"]') do
+ click_on(project.full_name)
end
- expect(find('.js-search-project-dropdown')).to have_content(project.full_name)
+ expect(find('[data-testid="project-filter"]')).to have_content(project.full_name)
end
context 'when the project filter is set' do
@@ -78,10 +80,10 @@ RSpec.describe 'User uses search filters', :js do
describe 'clear filter button' do
it 'removes Project filters' do
- link = find('[data-testid="project-filter"] .js-search-clear')
- params = CGI.parse(URI.parse(link[:href]).query)
+ find('[data-testid="project-filter"] [data-testid="clear-icon"]').click
+ wait_for_requests
- expect(params).not_to include(:project_id)
+ expect(page).to have_current_path(search_path(search: "test"))
end
end
end
diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb
index 38f00f399f3..8070ae066e7 100644
--- a/spec/features/security/admin_access_spec.rb
+++ b/spec/features/security/admin_access_spec.rb
@@ -8,7 +8,14 @@ RSpec.describe "Admin::Projects" do
describe "GET /admin/projects" do
subject { admin_projects_path }
- it { is_expected.to be_allowed_for :admin }
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed_for :admin }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.to be_denied_for :admin }
+ end
+
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :visitor }
end
@@ -16,7 +23,14 @@ RSpec.describe "Admin::Projects" do
describe "GET /admin/users" do
subject { admin_users_path }
- it { is_expected.to be_allowed_for :admin }
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed_for :admin }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.to be_denied_for :admin }
+ end
+
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :visitor }
end
@@ -24,7 +38,14 @@ RSpec.describe "Admin::Projects" do
describe "GET /admin/hooks" do
subject { admin_hooks_path }
- it { is_expected.to be_allowed_for :admin }
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed_for :admin }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.to be_denied_for :admin }
+ end
+
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :visitor }
end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 051bd601c1d..cb9f9a6e680 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -102,7 +102,8 @@ RSpec.describe "Internal Project Access" do
describe "GET /:project_path/-/settings/ci_cd" do
subject { project_settings_ci_cd_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -116,7 +117,8 @@ RSpec.describe "Internal Project Access" do
describe "GET /:project_path/-/settings/repository" do
subject { project_settings_repository_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -146,7 +148,8 @@ RSpec.describe "Internal Project Access" do
describe "GET /:project_path/edit" do
subject { edit_project_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -160,7 +163,8 @@ RSpec.describe "Internal Project Access" do
describe "GET /:project_path/deploy_keys" do
subject { project_deploy_keys_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -190,7 +194,8 @@ RSpec.describe "Internal Project Access" do
subject { edit_project_issue_path(project, issue) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -218,7 +223,8 @@ RSpec.describe "Internal Project Access" do
describe "GET /:project_path/snippets/new" do
subject { new_project_snippet_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -246,7 +252,8 @@ RSpec.describe "Internal Project Access" do
describe "GET /:project_path/-/merge_requests/new" do
subject { project_new_merge_request_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -302,7 +309,8 @@ RSpec.describe "Internal Project Access" do
describe "GET /:project_path/-/settings/integrations" do
subject { project_settings_integrations_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -367,7 +375,8 @@ RSpec.describe "Internal Project Access" do
project.update(public_builds: false)
end
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -406,7 +415,8 @@ RSpec.describe "Internal Project Access" do
project.update(public_builds: false)
end
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -445,7 +455,8 @@ RSpec.describe "Internal Project Access" do
project.update(public_builds: false)
end
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -460,7 +471,8 @@ RSpec.describe "Internal Project Access" do
describe "GET /:project_path/pipeline_schedules" do
subject { project_pipeline_schedules_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is disabled') { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -474,7 +486,8 @@ RSpec.describe "Internal Project Access" do
describe "GET /:project_path/-/environments" do
subject { project_environments_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is disabled') { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -490,7 +503,8 @@ RSpec.describe "Internal Project Access" do
subject { project_environment_path(project, environment) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is disabled') { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -506,7 +520,8 @@ RSpec.describe "Internal Project Access" do
subject { project_environment_deployments_path(project, environment) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is disabled') { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -520,7 +535,8 @@ RSpec.describe "Internal Project Access" do
describe "GET /:project_path/-/environments/new" do
subject { new_project_environment_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index e891e79db70..dda218c5de5 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -18,7 +18,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path" do
subject { project_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -32,7 +33,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/tree/master" do
subject { project_tree_path(project, project.repository.root_ref) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -46,7 +48,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/commits/master" do
subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -60,7 +63,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/commit/:sha" do
subject { project_commit_path(project, project.repository.commit) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -74,7 +78,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/compare" do
subject { project_compare_index_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -88,7 +93,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/project_members" do
subject { project_project_members_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -102,7 +108,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/settings/ci_cd" do
subject { project_settings_ci_cd_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -116,7 +123,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/settings/repository" do
subject { project_settings_repository_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -132,7 +140,8 @@ RSpec.describe "Private Project Access" do
subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -146,7 +155,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/edit" do
subject { edit_project_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -160,7 +170,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/deploy_keys" do
subject { project_deploy_keys_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -174,7 +185,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/issues" do
subject { project_issues_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -190,7 +202,8 @@ RSpec.describe "Private Project Access" do
subject { edit_project_issue_path(project, issue) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -204,7 +217,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/snippets" do
subject { project_snippets_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -218,7 +232,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/merge_requests" do
subject { project_merge_requests_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -239,7 +254,8 @@ RSpec.describe "Private Project Access" do
end
end
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -260,7 +276,8 @@ RSpec.describe "Private Project Access" do
end
end
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -274,7 +291,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/settings/integrations" do
subject { project_settings_integrations_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -288,7 +306,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/pipelines" do
subject { project_pipelines_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -316,7 +335,8 @@ RSpec.describe "Private Project Access" do
subject { project_pipeline_path(project, pipeline) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -342,7 +362,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/builds" do
subject { project_jobs_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -371,7 +392,8 @@ RSpec.describe "Private Project Access" do
subject { project_job_path(project, build.id) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -405,7 +427,8 @@ RSpec.describe "Private Project Access" do
subject { trace_project_job_path(project, build.id) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -435,7 +458,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/environments" do
subject { project_environments_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -451,7 +475,8 @@ RSpec.describe "Private Project Access" do
subject { project_environment_path(project, environment) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -467,7 +492,8 @@ RSpec.describe "Private Project Access" do
subject { project_environment_deployments_path(project, environment) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -481,7 +507,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/environments/new" do
subject { new_project_environment_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -495,7 +522,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/pipeline_schedules" do
subject { project_pipeline_schedules_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -509,7 +537,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/pipeline_schedules/new" do
subject { new_project_pipeline_schedule_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -523,7 +552,8 @@ RSpec.describe "Private Project Access" do
describe "GET /:project_path/-/environments/new" do
subject { new_project_pipeline_schedule_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -545,7 +575,8 @@ RSpec.describe "Private Project Access" do
subject { project_container_registry_index_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 75993959f6e..f2dbab72a48 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -102,7 +102,8 @@ RSpec.describe "Public Project Access" do
describe "GET /:project_path/-/settings/ci_cd" do
subject { project_settings_ci_cd_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -116,7 +117,8 @@ RSpec.describe "Public Project Access" do
describe "GET /:project_path/-/settings/repository" do
subject { project_settings_repository_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -181,7 +183,8 @@ RSpec.describe "Public Project Access" do
project.update(public_builds: false)
end
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -220,7 +223,8 @@ RSpec.describe "Public Project Access" do
project.update(public_builds: false)
end
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -259,7 +263,8 @@ RSpec.describe "Public Project Access" do
project.update(public_builds: false)
end
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -274,7 +279,8 @@ RSpec.describe "Public Project Access" do
describe "GET /:project_path/pipeline_schedules" do
subject { project_pipeline_schedules_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is disabled') { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -288,7 +294,8 @@ RSpec.describe "Public Project Access" do
describe "GET /:project_path/-/environments" do
subject { project_environments_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is disabled') { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -304,7 +311,8 @@ RSpec.describe "Public Project Access" do
subject { project_environment_path(project, environment) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is disabled') { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -320,7 +328,8 @@ RSpec.describe "Public Project Access" do
subject { project_environment_deployments_path(project, environment) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is disabled') { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -334,7 +343,8 @@ RSpec.describe "Public Project Access" do
describe "GET /:project_path/-/environments/new" do
subject { new_project_environment_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -363,7 +373,8 @@ RSpec.describe "Public Project Access" do
describe "GET /:project_path/edit" do
subject { edit_project_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -377,7 +388,8 @@ RSpec.describe "Public Project Access" do
describe "GET /:project_path/deploy_keys" do
subject { project_deploy_keys_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
@@ -407,7 +419,8 @@ RSpec.describe "Public Project Access" do
subject { edit_project_issue_path(project, issue) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -435,7 +448,8 @@ RSpec.describe "Public Project Access" do
describe "GET /:project_path/snippets/new" do
subject { new_project_snippet_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -463,7 +477,8 @@ RSpec.describe "Public Project Access" do
describe "GET /:project_path/-/merge_requests/new" do
subject { project_new_merge_request_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -519,7 +534,8 @@ RSpec.describe "Public Project Access" do
describe "GET /:project_path/-/settings/integrations" do
subject { project_settings_integrations_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_denied_for(:developer).of(project) }
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
index 0667a2fd48a..12237863188 100644
--- a/spec/features/security/project/snippet/internal_access_spec.rb
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -26,7 +26,8 @@ RSpec.describe "Internal Project Snippets Access" do
describe "GET /:project_path/snippets/new" do
subject { new_project_snippet_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -55,7 +56,8 @@ RSpec.describe "Internal Project Snippets Access" do
context "for a private snippet" do
subject { project_snippet_path(project, private_snippet) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -85,7 +87,8 @@ RSpec.describe "Internal Project Snippets Access" do
context "for a private snippet" do
subject { raw_project_snippet_path(project, private_snippet) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
index 0c97b012ad1..0f7ae06a6c5 100644
--- a/spec/features/security/project/snippet/private_access_spec.rb
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -12,7 +12,8 @@ RSpec.describe "Private Project Snippets Access" do
describe "GET /:project_path/snippets" do
subject { project_snippets_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -26,7 +27,8 @@ RSpec.describe "Private Project Snippets Access" do
describe "GET /:project_path/snippets/new" do
subject { new_project_snippet_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -40,7 +42,8 @@ RSpec.describe "Private Project Snippets Access" do
describe "GET /:project_path/snippets/:id for a private snippet" do
subject { project_snippet_path(project, private_snippet) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -54,7 +57,8 @@ RSpec.describe "Private Project Snippets Access" do
describe "GET /:project_path/snippets/:id/raw for a private snippet" do
subject { raw_project_snippet_path(project, private_snippet) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index 20a271f9c0e..2ae08205602 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -27,7 +27,8 @@ RSpec.describe "Public Project Snippets Access" do
describe "GET /:project_path/snippets/new" do
subject { new_project_snippet_path(project) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -70,7 +71,8 @@ RSpec.describe "Public Project Snippets Access" do
context "for a private snippet" do
subject { project_snippet_path(project, private_snippet) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
@@ -114,7 +116,8 @@ RSpec.describe "Public Project Snippets Access" do
context "for a private snippet" do
subject { raw_project_snippet_path(project, private_snippet) }
- it { is_expected.to be_allowed_for(:admin) }
+ it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) }
+ it('is denied for admin when admin mode is disabled') { is_expected.to be_denied_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
it { is_expected.to be_allowed_for(:maintainer).of(project) }
it { is_expected.to be_allowed_for(:developer).of(project) }
diff --git a/spec/features/snippets/private_snippets_spec.rb b/spec/features/snippets/private_snippets_spec.rb
index 03745c1025a..7ff27419cf7 100644
--- a/spec/features/snippets/private_snippets_spec.rb
+++ b/spec/features/snippets/private_snippets_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe 'Private Snippets', :js do
sign_in(user)
end
- it 'Private Snippet renders for creator' do
+ it 'private Snippet renders for creator' do
visit snippet_path(private_snippet)
wait_for_requests
diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb
index d2dc85a9614..0f27d96d8e9 100644
--- a/spec/features/snippets/public_snippets_spec.rb
+++ b/spec/features/snippets/public_snippets_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe 'Public Snippets', :js do
let(:public_snippet) { create(:personal_snippet, :public, :repository) }
let(:content) { public_snippet.blobs.first.data.strip! }
- it 'Unauthenticated user should see public snippets' do
+ it 'unauthenticated user should see public snippets' do
url = Gitlab::UrlBuilder.build(public_snippet)
visit snippet_path(public_snippet)
@@ -18,7 +18,7 @@ RSpec.describe 'Public Snippets', :js do
expect(page).to have_field('Share', readonly: true, with: url)
end
- it 'Unauthenticated user should see raw public snippets' do
+ it 'unauthenticated user should see raw public snippets' do
visit raw_snippet_path(public_snippet)
expect(page).to have_content(content)
diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb
index 4f299edc9da..46bc3b7caad 100644
--- a/spec/features/snippets/search_snippets_spec.rb
+++ b/spec/features/snippets/search_snippets_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'Search Snippets' do
- it 'User searches for snippets by title' do
+ it 'user searches for snippets by title' do
public_snippet = create(:personal_snippet, :public, title: 'Beginning and Middle')
private_snippet = create(:personal_snippet, :private, title: 'Middle and End')
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index 1e51210c2b8..ca050daa62a 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe 'User creates snippet', :js do
snippet_fill_in_form(title: title, content: file_content, description: md_description)
end
- it 'Authenticated user creates a snippet' do
+ it 'authenticated user creates a snippet' do
fill_form
click_button('Create snippet')
diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb
index a313dc3b26a..fe39208213a 100644
--- a/spec/features/snippets/user_snippets_spec.rb
+++ b/spec/features/snippets/user_snippets_spec.rb
@@ -13,13 +13,13 @@ RSpec.describe 'User Snippets' do
visit dashboard_snippets_path
end
- it 'View all of my snippets' do
+ it 'view all of my snippets' do
expect(page).to have_link(public_snippet.title, href: snippet_path(public_snippet))
expect(page).to have_link(internal_snippet.title, href: snippet_path(internal_snippet))
expect(page).to have_link(private_snippet.title, href: snippet_path(private_snippet))
end
- it 'View my public snippets' do
+ it 'view my public snippets' do
page.within('.snippet-scope-menu') do
click_link "Public"
end
@@ -29,7 +29,7 @@ RSpec.describe 'User Snippets' do
expect(page).not_to have_content(private_snippet.title)
end
- it 'View my internal snippets' do
+ it 'view my internal snippets' do
page.within('.snippet-scope-menu') do
click_link "Internal"
end
@@ -39,7 +39,7 @@ RSpec.describe 'User Snippets' do
expect(page).not_to have_content(private_snippet.title)
end
- it 'View my private snippets' do
+ it 'view my private snippets' do
page.within('.snippet-scope-menu') do
click_link "Private"
end
diff --git a/spec/features/usage_stats_consent_spec.rb b/spec/features/usage_stats_consent_spec.rb
index 04bdf25acc0..6fa1d7d76b5 100644
--- a/spec/features/usage_stats_consent_spec.rb
+++ b/spec/features/usage_stats_consent_spec.rb
@@ -19,6 +19,7 @@ RSpec.describe 'Usage stats consent' do
end
gitlab_sign_in(user)
+ gitlab_enable_admin_mode_sign_in(user)
end
it 'hides the banner permanently when sets usage stats' do
diff --git a/spec/features/users/active_sessions_spec.rb b/spec/features/users/active_sessions_spec.rb
index 8e2e16e555e..fab9f0884ae 100644
--- a/spec/features/users/active_sessions_spec.rb
+++ b/spec/features/users/active_sessions_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'Active user sessions', :clean_gitlab_redis_shared_state do
- it 'Successful login adds a new active user login' do
+ it 'successful login adds a new active user login' do
now = Time.zone.parse('2018-03-12 09:06')
Timecop.freeze(now) do
user = create(:user)
@@ -26,7 +26,7 @@ RSpec.describe 'Active user sessions', :clean_gitlab_redis_shared_state do
end
end
- it 'Successful login cleans up obsolete entries' do
+ it 'successful login cleans up obsolete entries' do
user = create(:user)
Gitlab::Redis::SharedState.with do |redis|
@@ -40,7 +40,7 @@ RSpec.describe 'Active user sessions', :clean_gitlab_redis_shared_state do
end
end
- it 'Sessionless login does not clean up obsolete entries' do
+ it 'sessionless login does not clean up obsolete entries' do
user = create(:user)
personal_access_token = create(:personal_access_token, user: user)
@@ -56,7 +56,7 @@ RSpec.describe 'Active user sessions', :clean_gitlab_redis_shared_state do
end
end
- it 'Logout deletes the active user login' do
+ it 'logout deletes the active user login' do
user = create(:user)
gitlab_sign_in(user)
expect(current_path).to eq root_path
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 0761c1871d3..e4a8d836413 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -742,28 +742,65 @@ RSpec.describe 'Login' do
end
context 'when the user did not enable 2FA' do
- it 'asks to set 2FA before asking to accept the terms' do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
+ context 'when `vue_2fa_recovery_codes` feature flag is disabled' do
+ before do
+ stub_feature_flags(vue_2fa_recovery_codes: false)
+ end
- visit new_user_session_path
+ it 'asks to set 2FA before asking to accept the terms' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
- fill_in 'user_login', with: user.email
- fill_in 'user_password', with: '12345678'
+ visit new_user_session_path
- click_button 'Sign in'
+ fill_in 'user_login', with: user.email
+ fill_in 'user_password', with: '12345678'
- expect_to_be_on_terms_page
- click_button 'Accept terms'
+ click_button 'Sign in'
+
+ expect_to_be_on_terms_page
+ click_button 'Accept terms'
+
+ expect(current_path).to eq(profile_two_factor_auth_path)
+
+ fill_in 'pin_code', with: user.reload.current_otp
- expect(current_path).to eq(profile_two_factor_auth_path)
+ click_button 'Register with two-factor app'
- fill_in 'pin_code', with: user.reload.current_otp
+ expect(page).to have_content('Congratulations! You have enabled Two-factor Authentication!')
- click_button 'Register with two-factor app'
- click_link 'Proceed'
+ click_link 'Proceed'
- expect(current_path).to eq(profile_account_path)
+ expect(current_path).to eq(profile_account_path)
+ end
+ end
+
+ context 'when `vue_2fa_recovery_codes` feature flag is enabled' do
+ it 'asks to set 2FA before asking to accept the terms', :js do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+
+ visit new_user_session_path
+
+ fill_in 'user_login', with: user.email
+ fill_in 'user_password', with: '12345678'
+
+ click_button 'Sign in'
+
+ expect_to_be_on_terms_page
+ click_button 'Accept terms'
+
+ expect(current_path).to eq(profile_two_factor_auth_path)
+
+ fill_in 'pin_code', with: user.reload.current_otp
+
+ click_button 'Register with two-factor app'
+ click_button 'Copy codes'
+ click_link 'Proceed'
+
+ expect(current_path).to eq(profile_account_path)
+ expect(page).to have_content('Congratulations! You have enabled Two-factor Authentication!')
+ end
end
end
diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb
index aebe2cc602d..6aeb3023db8 100644
--- a/spec/features/users/show_spec.rb
+++ b/spec/features/users/show_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe 'User page' do
let_it_be(:user) { create(:user, bio: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') }
- subject { visit(user_path(user)) }
+ subject(:visit_profile) { visit(user_path(user)) }
context 'with public profile' do
it 'shows all the tabs' do
@@ -123,6 +123,47 @@ RSpec.describe 'User page' do
end
end
+ context 'with unconfirmed user' do
+ let_it_be(:user) { create(:user, :unconfirmed) }
+
+ shared_examples 'unconfirmed user profile' do
+ before do
+ visit_profile
+ end
+
+ it 'shows user name as unconfirmed' do
+ expect(page).to have_css(".cover-title", text: 'Unconfirmed user')
+ end
+
+ it 'shows no tab' do
+ expect(page).to have_css("div.profile-header")
+ expect(page).not_to have_css("ul.nav-links")
+ end
+
+ it 'shows no additional fields' do
+ expect(page).not_to have_css(".profile-user-bio")
+ expect(page).not_to have_css(".profile-link-holder")
+ end
+
+ it 'shows private profile message' do
+ expect(page).to have_content("This user has a private profile")
+ end
+ end
+
+ context 'when visited by an authenticated user' do
+ before do
+ authenticated_user = create(:user)
+ sign_in(authenticated_user)
+ end
+
+ it_behaves_like 'unconfirmed user profile'
+ end
+
+ context 'when visited by an unauthenticated user' do
+ it_behaves_like 'unconfirmed user profile'
+ end
+ end
+
it 'shows the status if there was one' do
create(:user_status, user: user, message: "Working hard!")
diff --git a/spec/finders/alert_management/alerts_finder_spec.rb b/spec/finders/alert_management/alerts_finder_spec.rb
index e74f3ac68ed..87a5da38dd1 100644
--- a/spec/finders/alert_management/alerts_finder_spec.rb
+++ b/spec/finders/alert_management/alerts_finder_spec.rb
@@ -8,6 +8,8 @@ RSpec.describe AlertManagement::AlertsFinder, '#execute' do
let_it_be(:resolved_alert) { create(:alert_management_alert, :all_fields, :resolved, project: project, ended_at: 1.year.ago, events: 2, severity: :high) }
let_it_be(:ignored_alert) { create(:alert_management_alert, :all_fields, :ignored, project: project, events: 1, severity: :critical) }
let_it_be(:triggered_alert) { create(:alert_management_alert, :all_fields) }
+ let_it_be(:threat_monitroing_alert) { create(:alert_management_alert, domain: 'threat_monitoring') }
+
let(:params) { {} }
describe '#execute' do
@@ -22,6 +24,26 @@ RSpec.describe AlertManagement::AlertsFinder, '#execute' do
project.add_developer(current_user)
end
+ context 'domain' do
+ context 'domain is threat management' do
+ let(:params) { { domain: 'threat_management' } }
+
+ it { is_expected.to contain_exactly(resolved_alert, ignored_alert) }
+ end
+
+ context 'domain is unknown' do
+ let(:params) { { domain: 'unkown' } }
+
+ it { is_expected.to contain_exactly(resolved_alert, ignored_alert) }
+ end
+
+ context 'domain is missing' do
+ let(:params) { {} }
+
+ it { is_expected.to contain_exactly(resolved_alert, ignored_alert) }
+ end
+ end
+
context 'empty params' do
it { is_expected.to contain_exactly(resolved_alert, ignored_alert) }
end
@@ -233,12 +255,6 @@ RSpec.describe AlertManagement::AlertsFinder, '#execute' do
it { is_expected.to be_empty }
end
-
- context 'empty search' do
- let(:params) { { search: ' ' } }
-
- it { is_expected.not_to include(alert) }
- end
end
context 'assignee username given' do
@@ -257,12 +273,6 @@ RSpec.describe AlertManagement::AlertsFinder, '#execute' do
it { is_expected.to be_empty }
end
-
- context 'with empty assignee_username' do
- let(:username) { ' ' }
-
- it { is_expected.not_to include(alert) }
- end
end
end
end
diff --git a/spec/finders/ci/daily_build_group_report_results_finder_spec.rb b/spec/finders/ci/daily_build_group_report_results_finder_spec.rb
index c0434b5f371..28a732fda82 100644
--- a/spec/finders/ci/daily_build_group_report_results_finder_spec.rb
+++ b/spec/finders/ci/daily_build_group_report_results_finder_spec.rb
@@ -4,57 +4,77 @@ require 'spec_helper'
RSpec.describe Ci::DailyBuildGroupReportResultsFinder do
describe '#execute' do
- let(:project) { create(:project, :private) }
- let(:ref_path) { 'refs/heads/master' }
+ let_it_be(:project) { create(:project, :private) }
+ let_it_be(:current_user) { project.owner }
+ let_it_be(:ref_path) { 'refs/heads/master' }
let(:limit) { nil }
+ let_it_be(:default_branch) { false }
- let!(:rspec_coverage_1) { create_daily_coverage('rspec', 79.0, '2020-03-09') }
- let!(:karma_coverage_1) { create_daily_coverage('karma', 89.0, '2020-03-09') }
- let!(:rspec_coverage_2) { create_daily_coverage('rspec', 95.0, '2020-03-10') }
- let!(:karma_coverage_2) { create_daily_coverage('karma', 92.0, '2020-03-10') }
- let!(:rspec_coverage_3) { create_daily_coverage('rspec', 97.0, '2020-03-11') }
- let!(:karma_coverage_3) { create_daily_coverage('karma', 99.0, '2020-03-11') }
+ let_it_be(:rspec_coverage_1) { create_daily_coverage('rspec', 79.0, '2020-03-09') }
+ let_it_be(:karma_coverage_1) { create_daily_coverage('karma', 89.0, '2020-03-09') }
+ let_it_be(:rspec_coverage_2) { create_daily_coverage('rspec', 95.0, '2020-03-10') }
+ let_it_be(:karma_coverage_2) { create_daily_coverage('karma', 92.0, '2020-03-10') }
+ let_it_be(:rspec_coverage_3) { create_daily_coverage('rspec', 97.0, '2020-03-11') }
+ let_it_be(:karma_coverage_3) { create_daily_coverage('karma', 99.0, '2020-03-11') }
- subject do
- described_class.new(
+ let(:attributes) do
+ {
current_user: current_user,
project: project,
ref_path: ref_path,
start_date: '2020-03-09',
end_date: '2020-03-10',
limit: limit
- ).execute
+ }
end
- context 'when current user is allowed to read build report results' do
- let(:current_user) { project.owner }
+ subject(:coverages) do
+ described_class.new(**attributes).execute
+ end
+
+ context 'when ref_path is present' do
+ context 'when current user is allowed to read build report results' do
+ it 'returns all matching results within the given date range' do
+ expect(coverages).to match_array([
+ karma_coverage_2,
+ rspec_coverage_2,
+ karma_coverage_1,
+ rspec_coverage_1
+ ])
+ end
+
+ context 'and limit is specified' do
+ let(:limit) { 2 }
- it 'returns all matching results within the given date range' do
- expect(subject).to match_array([
- karma_coverage_2,
- rspec_coverage_2,
- karma_coverage_1,
- rspec_coverage_1
- ])
+ it 'returns limited number of matching results within the given date range' do
+ expect(coverages).to match_array([
+ karma_coverage_2,
+ rspec_coverage_2
+ ])
+ end
+ end
end
- context 'and limit is specified' do
- let(:limit) { 2 }
+ context 'when current user is not allowed to read build report results' do
+ let(:current_user) { create(:user) }
- it 'returns limited number of matching results within the given date range' do
- expect(subject).to match_array([
- karma_coverage_2,
- rspec_coverage_2
- ])
+ it 'returns an empty result' do
+ expect(coverages).to be_empty
end
end
end
- context 'when current user is not allowed to read build report results' do
- let(:current_user) { create(:user) }
+ context 'when ref_path is not present' do
+ let(:ref_path) { nil }
- it 'returns an empty result' do
- expect(subject).to be_empty
+ context 'when coverages exist for the default branch' do
+ let(:default_branch) { true }
+
+ it 'returns coverage for the default branch' do
+ rspec_coverage_4 = create_daily_coverage('rspec', 66.0, '2020-03-10')
+
+ expect(coverages).to contain_exactly(rspec_coverage_4)
+ end
end
end
end
@@ -65,10 +85,11 @@ RSpec.describe Ci::DailyBuildGroupReportResultsFinder do
create(
:ci_daily_build_group_report_result,
project: project,
- ref_path: ref_path,
+ ref_path: ref_path || 'feature-branch',
group_name: group_name,
data: { 'coverage' => coverage },
- date: date
+ date: date,
+ default_branch: default_branch
)
end
end
diff --git a/spec/finders/ci/pipeline_schedules_finder_spec.rb b/spec/finders/ci/pipeline_schedules_finder_spec.rb
index 57842bbecd7..535c684289e 100644
--- a/spec/finders/ci/pipeline_schedules_finder_spec.rb
+++ b/spec/finders/ci/pipeline_schedules_finder_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Ci::PipelineSchedulesFinder do
let!(:active_schedule) { create(:ci_pipeline_schedule, project: project) }
let!(:inactive_schedule) { create(:ci_pipeline_schedule, :inactive, project: project) }
- subject { described_class.new(project).execute(params) }
+ subject { described_class.new(project).execute(**params) }
describe "#execute" do
context 'when the scope is nil' do
diff --git a/spec/finders/ci/pipelines_finder_spec.rb b/spec/finders/ci/pipelines_finder_spec.rb
index a2a714689ba..16561aa65b6 100644
--- a/spec/finders/ci/pipelines_finder_spec.rb
+++ b/spec/finders/ci/pipelines_finder_spec.rb
@@ -72,7 +72,7 @@ RSpec.describe Ci::PipelinesFinder do
create(:ci_sources_pipeline, pipeline: child_pipeline, source_pipeline: parent_pipeline)
end
- it 'filters out child pipelines and show only the parents' do
+ it 'filters out child pipelines and shows only the parents by default' do
is_expected.to eq([parent_pipeline])
end
end
@@ -195,6 +195,21 @@ RSpec.describe Ci::PipelinesFinder do
end
end
+ context 'when iids filter is specified' do
+ let(:params) { { iids: [pipeline1.iid, pipeline3.iid] } }
+ let!(:pipeline1) { create(:ci_pipeline, project: project) }
+ let!(:pipeline2) { create(:ci_pipeline, project: project) }
+ let!(:pipeline3) { create(:ci_pipeline, project: project, source: :parent_pipeline) }
+
+ it 'returns matches pipelines' do
+ is_expected.to match_array([pipeline1, pipeline3])
+ end
+
+ it 'does not fitler out child pipelines' do
+ is_expected.to include(pipeline3)
+ end
+ end
+
context 'when sha is specified' do
let!(:pipeline) { create(:ci_pipeline, project: project, sha: '97de212e80737a608d939f648d959671fb0a0142') }
diff --git a/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb b/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb
index 65f6dc0ba74..64b3c46e122 100644
--- a/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb
+++ b/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb
@@ -225,6 +225,24 @@ RSpec.describe Ci::PipelinesForMergeRequestFinder do
branch_pipeline_2,
branch_pipeline])
end
+
+ context 'when ci_pipelines_for_merge_request_finder_new_cte feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_pipelines_for_merge_request_finder_new_cte: false)
+ end
+
+ it 'returns only related merge request pipelines' do
+ expect(subject.all)
+ .to eq([detached_merge_request_pipeline,
+ branch_pipeline_2,
+ branch_pipeline])
+
+ expect(described_class.new(merge_request_2, nil).all)
+ .to eq([detached_merge_request_pipeline_2,
+ branch_pipeline_2,
+ branch_pipeline])
+ end
+ end
end
context 'when detached merge request pipeline is run on head ref of the merge request' do
diff --git a/spec/finders/feature_flags_finder_spec.rb b/spec/finders/feature_flags_finder_spec.rb
index 870447a1286..8744a186212 100644
--- a/spec/finders/feature_flags_finder_spec.rb
+++ b/spec/finders/feature_flags_finder_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe FeatureFlagsFinder do
end
describe '#execute' do
- subject { finder.execute(args) }
+ subject { finder.execute(**args) }
let!(:feature_flag_1) { create(:operations_feature_flag, name: 'flag-a', project: project) }
let!(:feature_flag_2) { create(:operations_feature_flag, name: 'flag-b', project: project) }
@@ -80,15 +80,5 @@ RSpec.describe FeatureFlagsFinder do
is_expected.to eq([feature_flag_1, feature_flag_2, feature_flag_3])
end
end
-
- context 'when new version flags are disabled' do
- let!(:feature_flag_3) { create(:operations_feature_flag, :new_version_flag, name: 'flag-c', project: project) }
-
- it 'returns only legacy flags' do
- stub_feature_flags(feature_flags_new_version: false)
-
- is_expected.to eq([feature_flag_1, feature_flag_2])
- end
- end
end
end
diff --git a/spec/finders/fork_projects_finder_spec.rb b/spec/finders/fork_projects_finder_spec.rb
index 9e58378b953..2b2e4c0d618 100644
--- a/spec/finders/fork_projects_finder_spec.rb
+++ b/spec/finders/fork_projects_finder_spec.rb
@@ -14,8 +14,6 @@ RSpec.describe ForkProjectsFinder do
let(:private_fork_member) { create(:user) }
before do
- stub_feature_flags(object_pools: source_project)
-
private_fork.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
private_fork.add_developer(private_fork_member)
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index b66d0ffce87..3c3bb13a629 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -77,9 +77,9 @@ RSpec.describe GroupDescendantsFinder do
end
end
- it 'sorts elements by latest created as default' do
- project1 = create(:project, namespace: group, created_at: 1.hour.ago)
- project2 = create(:project, namespace: group)
+ it 'sorts elements by name as default' do
+ project1 = create(:project, namespace: group, name: 'z')
+ project2 = create(:project, namespace: group, name: 'a')
expect(subject.execute).to eq([project2, project1])
end
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index 67e7de5921a..a87a05d4408 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -129,4 +129,48 @@ RSpec.describe GroupMembersFinder, '#execute' do
expect(result.to_a).not_to include(member_with_2fa)
expect(result.to_a).to match_array([member1, member2])
end
+
+ it 'returns direct members with two-factor auth if requested by owner' do
+ group.add_owner(user1)
+ group.add_maintainer(user2)
+ nested_group.add_maintainer(user3)
+ member_with_2fa = nested_group.add_maintainer(user5)
+
+ result = described_class.new(nested_group, user1, params: { two_factor: 'enabled' }).execute(include_relations: [:direct])
+
+ expect(result.to_a).to match_array([member_with_2fa])
+ end
+
+ it 'returns inherited members with two-factor auth if requested by owner' do
+ group.add_owner(user1)
+ member_with_2fa = group.add_maintainer(user5)
+ nested_group.add_maintainer(user2)
+ nested_group.add_maintainer(user3)
+
+ result = described_class.new(nested_group, user1, params: { two_factor: 'enabled' }).execute(include_relations: [:inherited])
+
+ expect(result.to_a).to match_array([member_with_2fa])
+ end
+
+ it 'returns direct members without two-factor auth if requested by owner' do
+ group.add_owner(user1)
+ group.add_maintainer(user2)
+ member3 = nested_group.add_maintainer(user3)
+ nested_group.add_maintainer(user5)
+
+ result = described_class.new(nested_group, user1, params: { two_factor: 'disabled' }).execute(include_relations: [:direct])
+
+ expect(result.to_a).to match_array([member3])
+ end
+
+ it 'returns inherited members without two-factor auth if requested by owner' do
+ member1 = group.add_owner(user1)
+ group.add_maintainer(user5)
+ nested_group.add_maintainer(user2)
+ nested_group.add_maintainer(user3)
+
+ result = described_class.new(nested_group, user1, params: { two_factor: 'disabled' }).execute(include_relations: [:inherited])
+
+ expect(result.to_a).to match_array([member1])
+ end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 3c3bf1a8870..0def3412aa7 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -472,10 +472,6 @@ RSpec.describe IssuesFinder do
it 'returns issues with title and description match for search term' do
expect(issues).to contain_exactly(issue1, issue2)
end
-
- it 'uses optimizer hints' do
- expect(issues.to_sql).to match(/BitmapScan/)
- end
end
context 'filtering by issue term in title' do
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index 3530858e2de..d25e1b9ca4b 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -160,8 +160,8 @@ RSpec.describe MembersFinder, '#execute' do
expect(result).to eq([member3, member2, member1])
end
- context 'when include_invited_groups_members == true' do
- subject { described_class.new(project, user2).execute(include_relations: [:inherited, :direct, :invited_groups_members]) }
+ context 'when :invited_groups is passed' do
+ subject { described_class.new(project, user2).execute(include_relations: [:inherited, :direct, :invited_groups]) }
let_it_be(:linked_group) { create(:group, :public) }
let_it_be(:nested_linked_group) { create(:group, parent: linked_group) }
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 68958e37001..7b59b581b1c 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -333,6 +333,8 @@ RSpec.describe MergeRequestsFinder do
end
context 'assignee filtering' do
+ let_it_be(:user3) { create(:user) }
+
let(:issuables) { described_class.new(user, params).execute }
it_behaves_like 'assignee ID filter' do
@@ -351,7 +353,6 @@ RSpec.describe MergeRequestsFinder do
merge_request3.assignees = [user2, user3]
end
- let_it_be(:user3) { create(:user) }
let(:params) { { assignee_username: [user2.username, user3.username] } }
let(:expected_issuables) { [merge_request3] }
end
@@ -366,38 +367,95 @@ RSpec.describe MergeRequestsFinder do
end
it_behaves_like 'no assignee filter' do
- let_it_be(:user3) { create(:user) }
let(:expected_issuables) { [merge_request4, merge_request5] }
end
it_behaves_like 'any assignee filter' do
let(:expected_issuables) { [merge_request1, merge_request2, merge_request3] }
end
+ end
- context 'filtering by group milestone' do
- let(:group_milestone) { create(:milestone, group: group) }
+ context 'reviewer filtering' do
+ subject { described_class.new(user, params).execute }
- before do
- merge_request1.update!(milestone: group_milestone)
- merge_request2.update!(milestone: group_milestone)
- end
+ context 'by reviewer_id' do
+ let(:params) { { reviewer_id: user2.id } }
+ let(:expected_mr) { [merge_request1, merge_request2] }
- it 'returns merge requests assigned to that group milestone' do
- params = { milestone_title: group_milestone.title }
+ it { is_expected.to contain_exactly(*expected_mr) }
+ end
- merge_requests = described_class.new(user, params).execute
+ context 'by NOT reviewer_id' do
+ let(:params) { { not: { reviewer_id: user2.id } } }
+ let(:expected_mr) { [merge_request3, merge_request4, merge_request5] }
- expect(merge_requests).to contain_exactly(merge_request1, merge_request2)
- end
+ it { is_expected.to contain_exactly(*expected_mr) }
+ end
- context 'using NOT' do
- let(:params) { { not: { milestone_title: group_milestone.title } } }
+ context 'by reviewer_username' do
+ let(:params) { { reviewer_username: user2.username } }
+ let(:expected_mr) { [merge_request1, merge_request2] }
- it 'returns MRs not assigned to that group milestone' do
- merge_requests = described_class.new(user, params).execute
+ it { is_expected.to contain_exactly(*expected_mr) }
+ end
- expect(merge_requests).to contain_exactly(merge_request3, merge_request4, merge_request5)
- end
+ context 'by NOT reviewer_username' do
+ let(:params) { { not: { reviewer_username: user2.username } } }
+ let(:expected_mr) { [merge_request3, merge_request4, merge_request5] }
+
+ it { is_expected.to contain_exactly(*expected_mr) }
+ end
+
+ context 'by reviewer_id=None' do
+ let(:params) { { reviewer_id: 'None' } }
+ let(:expected_mr) { [merge_request4, merge_request5] }
+
+ it { is_expected.to contain_exactly(*expected_mr) }
+ end
+
+ context 'by reviewer_id=Any' do
+ let(:params) { { reviewer_id: 'Any' } }
+ let(:expected_mr) { [merge_request1, merge_request2, merge_request3] }
+
+ it { is_expected.to contain_exactly(*expected_mr) }
+ end
+
+ context 'by reviewer_id with unknown user' do
+ let(:params) { { reviewer_id: 99999 } }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'by NOT reviewer_id with unknown user' do
+ let(:params) { { not: { reviewer_id: 99999 } } }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ context 'filtering by group milestone' do
+ let(:group_milestone) { create(:milestone, group: group) }
+
+ before do
+ merge_request1.update!(milestone: group_milestone)
+ merge_request2.update!(milestone: group_milestone)
+ end
+
+ it 'returns merge requests assigned to that group milestone' do
+ params = { milestone_title: group_milestone.title }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2)
+ end
+
+ context 'using NOT' do
+ let(:params) { { not: { milestone_title: group_milestone.title } } }
+
+ it 'returns MRs not assigned to that group milestone' do
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request3, merge_request4, merge_request5)
end
end
end
@@ -563,6 +621,28 @@ RSpec.describe MergeRequestsFinder do
expect(mrs).to eq([mr2])
end
end
+
+ it 'does not raise any exception with complex filters' do
+ # available filters from MergeRequest dashboard UI
+ params = {
+ project_id: project1.id,
+ scope: 'authored',
+ state: 'opened',
+ author_username: user.username,
+ assignee_username: user.username,
+ reviewer_username: user.username,
+ approver_usernames: [user.username],
+ approved_by_usernames: [user.username],
+ milestone_title: 'none',
+ release_tag: 'none',
+ label_names: 'none',
+ my_reaction_emoji: 'none',
+ draft: 'no'
+ }
+
+ merge_requests = described_class.new(user, params).execute
+ expect { merge_requests.load }.not_to raise_error
+ end
end
describe '#row_count', :request_store do
diff --git a/spec/finders/releases/evidence_pipeline_finder_spec.rb b/spec/finders/releases/evidence_pipeline_finder_spec.rb
new file mode 100644
index 00000000000..8c435f7b0b6
--- /dev/null
+++ b/spec/finders/releases/evidence_pipeline_finder_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Releases::EvidencePipelineFinder, '#execute' do
+ let(:params) { {} }
+ let(:project) { create(:project, :repository) }
+ let(:tag_name) { project.repository.tag_names.first }
+ let(:sha) { project.repository.find_tag(tag_name).dereferenced_target.sha }
+ let!(:pipeline) { create(:ci_empty_pipeline, sha: sha, project: project) }
+
+ subject { described_class.new(project, params).execute }
+
+ context 'when the tag is passed' do
+ let(:params) { { tag: tag_name } }
+
+ it 'returns the evidence pipeline' do
+ expect(subject).to eq(pipeline)
+ end
+ end
+
+ context 'when the ref is passed' do
+ let(:params) { { ref: sha } }
+
+ it 'returns the evidence pipeline' do
+ expect(subject).to eq(pipeline)
+ end
+ end
+
+ context 'empty params' do
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+
+ # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245
+ context 'params[:evidence_pipeline] is present' do
+ let(:params) { { evidence_pipeline: pipeline } }
+
+ it 'returns the passed evidence pipeline' do
+ expect(subject).to eq(pipeline)
+ end
+ end
+end
diff --git a/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json b/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json
new file mode 100644
index 00000000000..eab8b626876
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json
@@ -0,0 +1,30 @@
+{
+ "type": "object",
+ "properties": {
+ "edit": { "type": "string" },
+ "approve": { "type": "string" },
+ "reject": { "type": "string" },
+ "unblock": { "type": "string" },
+ "block": { "type": "string" },
+ "deactivate": { "type": "string" },
+ "activate": { "type": "string" },
+ "unlock": { "type": "string" },
+ "delete": { "type": "string" },
+ "delete_with_contributions": { "type": "string" },
+ "admin_user": { "type": "string" }
+ },
+ "required": [
+ "edit",
+ "approve",
+ "reject",
+ "unblock",
+ "block",
+ "deactivate",
+ "activate",
+ "unlock",
+ "delete",
+ "delete_with_contributions",
+ "admin_user"
+ ],
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/codequality_degradation.json b/spec/fixtures/api/schemas/entities/codequality_degradation.json
new file mode 100644
index 00000000000..6cf20ee8b9e
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/codequality_degradation.json
@@ -0,0 +1,24 @@
+{
+ "type": "object",
+ "required": [
+ "description",
+ "severity",
+ "file_path",
+ "line"
+ ],
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "severity": {
+ "type": "string"
+ },
+ "file_path": {
+ "type": "string"
+ },
+ "line": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/codequality_reports_comparer.json b/spec/fixtures/api/schemas/entities/codequality_reports_comparer.json
new file mode 100644
index 00000000000..afe82f5e632
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/codequality_reports_comparer.json
@@ -0,0 +1,43 @@
+{
+ "type": "object",
+ "required": ["status", "summary", "new_errors", "resolved_errors", "existing_errors"],
+ "properties": {
+ "status": {
+ "type": "string"
+ },
+ "summary": {
+ "type": "object",
+ "properties": {
+ "total": {
+ "type": "integer"
+ },
+ "resolved": {
+ "type": "integer"
+ },
+ "errored": {
+ "type": "integer"
+ }
+ },
+ "required": ["total", "resolved", "errored"]
+ },
+ "new_errors": {
+ "type": "array",
+ "items": {
+ "$ref": "codequality_degradation.json"
+ }
+ },
+ "resolved_errors": {
+ "type": "array",
+ "items": {
+ "$ref": "codequality_degradation.json"
+ }
+ },
+ "existing_errors": {
+ "type": "array",
+ "items": {
+ "$ref": "codequality_degradation.json"
+ }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index e2df7952d8f..c90f7af5892 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -14,6 +14,7 @@
"merge_request_cached_widget_path": { "type": "string" },
"commit_change_content_path": { "type": "string" },
"conflicts_docs_path": { "type": ["string", "null"] },
+ "reviewing_and_managing_merge_requests_docs_path": { "type": ["string", "null"] },
"merge_request_pipelines_docs_path": { "type": ["string", "null"] },
"ci_environments_status_path": { "type": "string" },
"issues_links": {
diff --git a/spec/fixtures/api/schemas/entities/note_user_entity.json b/spec/fixtures/api/schemas/entities/note_user_entity.json
index 4a27d885cdc..e2bbaad7201 100644
--- a/spec/fixtures/api/schemas/entities/note_user_entity.json
+++ b/spec/fixtures/api/schemas/entities/note_user_entity.json
@@ -15,6 +15,7 @@
"path": { "type": "string" },
"name": { "type": "string" },
"username": { "type": "string" },
- "status_tooltip_html": { "$ref": "../types/nullable_string.json" }
+ "status_tooltip_html": { "$ref": "../types/nullable_string.json" },
+ "show_status": { "type": "boolean" }
}
}
diff --git a/spec/fixtures/api/schemas/graphql/container_repository.json b/spec/fixtures/api/schemas/graphql/container_repository.json
index e252bedab82..04e67f73844 100644
--- a/spec/fixtures/api/schemas/graphql/container_repository.json
+++ b/spec/fixtures/api/schemas/graphql/container_repository.json
@@ -1,6 +1,6 @@
{
"type": "object",
- "required": ["id", "name", "path", "location", "createdAt", "updatedAt", "tagsCount", "canDelete", "expirationPolicyCleanupStatus"],
+ "required": ["id", "name", "path", "location", "createdAt", "updatedAt", "tagsCount", "canDelete", "expirationPolicyCleanupStatus", "project"],
"properties": {
"id": {
"type": "string"
@@ -35,6 +35,9 @@
"expirationPolicyCleanupStatus": {
"type": "string",
"enum": ["UNSCHEDULED", "SCHEDULED", "UNFINISHED", "ONGOING"]
+ },
+ "project": {
+ "type": "object"
}
}
}
diff --git a/spec/fixtures/api/schemas/list.json b/spec/fixtures/api/schemas/list.json
index f894ff584c8..65e140f9e28 100644
--- a/spec/fixtures/api/schemas/list.json
+++ b/spec/fixtures/api/schemas/list.json
@@ -10,7 +10,7 @@
"id": { "type": "integer" },
"list_type": {
"type": "string",
- "enum": ["backlog", "label", "closed"]
+ "enum": ["backlog", "label", "iteration", "closed"]
},
"label": {
"type": ["object", "null"],
diff --git a/spec/fixtures/api/schemas/public_api/v4/issue_link.json b/spec/fixtures/api/schemas/public_api/v4/issue_link.json
index 000e8485aca..588d63c2dcf 100644
--- a/spec/fixtures/api/schemas/public_api/v4/issue_link.json
+++ b/spec/fixtures/api/schemas/public_api/v4/issue_link.json
@@ -14,7 +14,9 @@
"link_type": {
"type": "string",
"enum": ["relates_to", "blocks", "is_blocked_by"]
- }
+ },
+ "link_created_at": { "type": "date" },
+ "link_updated_at": { "type": "date" }
},
"required" : [ "source_issue", "target_issue", "link_type" ]
}
diff --git a/spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz b/spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz
new file mode 100644
index 00000000000..c4adc63fcce
--- /dev/null
+++ b/spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz
Binary files differ
diff --git a/spec/fixtures/codequality/codeclimate.json b/spec/fixtures/codequality/codeclimate.json
new file mode 100644
index 00000000000..ceb28006304
--- /dev/null
+++ b/spec/fixtures/codequality/codeclimate.json
@@ -0,0 +1,76 @@
+[
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ },
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_backwards_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "f3bdc1e8c102ba5fbd9e7f6cda51c95e",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 14,
+ "end": 14
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ },
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 24,
+ "line": 14
+ },
+ "end": {
+ "column": 49,
+ "line": 14
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }
+]
diff --git a/spec/fixtures/codequality/codeclimate_without_errors.json b/spec/fixtures/codequality/codeclimate_without_errors.json
new file mode 100644
index 00000000000..fe51488c706
--- /dev/null
+++ b/spec/fixtures/codequality/codeclimate_without_errors.json
@@ -0,0 +1 @@
+[]
diff --git a/spec/fixtures/codequality/codequality.json b/spec/fixtures/codequality/codequality.json
deleted file mode 100644
index 89cc4b46521..00000000000
--- a/spec/fixtures/codequality/codequality.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `simulateDrag` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a995a617b0ce0599b6d0a5649a308a8e","location":{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":75,"end":137}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `simulateEvent` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e50b54fce33185efbb17d1c0d210b439","location":{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":1,"end":37}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `simulateDrag` has 51 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"75ffb8892dac22a9d50822bc69339ab2","location":{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":75,"end":137}},"other_locations":[],"remediation_points":1224000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"40f188d222bd1f110e8c11f08ec77c3f","location":{"path":"app/assets/javascripts/blob/3d_viewer/index.js","lines":{"begin":10,"end":47}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 60 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"95b71f07d08eb9e2368568c6d872f677","location":{"path":"app/assets/javascripts/blob/blob_file_dropzone.js","lines":{"begin":21,"end":88}},"other_locations":[],"remediation_points":1440000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"`FileTemplateMediator` has 29 functions (exceeds 20 allowed). Consider refactoring.","fingerprint":"02a64abfacf06440f6504888503fe571","location":{"path":"app/assets/javascripts/blob/file_template_mediator.js","lines":{"begin":11,"end":246}},"other_locations":[],"remediation_points":2100000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ede6d8c5878fbfdda2348d1c6a808f4b","location":{"path":"app/assets/javascripts/issuable_form.js","lines":{"begin":14,"end":57}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initTargetBranchDropdown` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"748b8a39304544f015c0abcbee9b929c","location":{"path":"app/assets/javascripts/issuable_form.js","lines":{"begin":116,"end":147}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `start` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b0272210e1d618fe95522ee47f652308","location":{"path":"app/assets/javascripts/smart_interval.js","lines":{"begin":44,"end":65}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `render` has 38 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ceb92ce0f43dbe13431f1f07d5787aff","location":{"path":"app/assets/javascripts/vue_shared/components/tabs/tabs.js","lines":{"begin":35,"end":75}},"other_locations":[],"remediation_points":912000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `file_icon_map.js` has 587 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"33d443c482c7a34a3a721ef8677a9b9b","location":{"path":"app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js","lines":{"begin":1,"end":590}},"other_locations":[],"remediation_points":6052800,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `gl_dropdown.js` has 935 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"144655d5e2aa528a6e4d63861c3bc2f1","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":1,"end":1111}},"other_locations":[],"remediation_points":11064000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `GitLabDropdownInput` has 41 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"4679297a043d89d437946bd083443d13","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":14,"end":58}},"other_locations":[],"remediation_points":984000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `GitLabDropdownFilter` has 55 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"0c2b6bfd5d744ae2f9724aae0a60a765","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":76,"end":136}},"other_locations":[],"remediation_points":1320000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `filter` has 53 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"3be214723bb15219db661db421dccba1","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":142,"end":213}},"other_locations":[],"remediation_points":1272000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `GitLabDropdown` has 172 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"45e3d28c25ef43db19fcd1f8bf7a52dc","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":296,"end":481}},"other_locations":[],"remediation_points":4128000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `parseData` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"5a6929b08a7efc020e932267316a1ce4","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":505,"end":538}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `opened` has 36 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"b32257d5ff71fd022c46be4ff3f196b6","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":580,"end":627}},"other_locations":[],"remediation_points":864000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `renderItem` has 77 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"119c9b263aba3cc2173646b63b83d58d","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":698,"end":793}},"other_locations":[],"remediation_points":1848000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `rowClicked` has 75 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e37288d65d5701c4b7d06560d3eb6910","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":820,"end":904}},"other_locations":[],"remediation_points":1800000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `addArrowKeyEvent` has 39 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"4ccf81453af86fd4e7f521423adbf28e","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":971,"end":1015}},"other_locations":[],"remediation_points":936000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `highlightRowAtIndex` has 36 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"a0b5b8a75d9f5726f8a559a3068bbfd9","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":1026,"end":1073}},"other_locations":[],"remediation_points":864000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"nested_control_flow","content":{"body":""},"description":"Avoid deeply nested control flow statements.","location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":174,"end":184}},"other_locations":[],"remediation_points":450000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"603368a717652923897a9efd4f494784"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `onOptionClick` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a1ead8da9cc948075715df57538a53fb","location":{"path":"app/assets/javascripts/groups/groups_filterable_list.js","lines":{"begin":76,"end":122}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `onOptionClick` has 32 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"aa9f5f9cb36f5307a7db44c29b5f3a4c","location":{"path":"app/assets/javascripts/groups/groups_filterable_list.js","lines":{"begin":76,"end":122}},"other_locations":[],"remediation_points":768000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `setSearchedGroups` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4116f1bf48693b8dbc3a19233c167489","location":{"path":"app/assets/javascripts/groups/store/groups_store.js","lines":{"begin":19,"end":33}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `formatGroupItem` has 31 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"97209b6e238a05d220f90d1e1fcf7c78","location":{"path":"app/assets/javascripts/groups/store/groups_store.js","lines":{"begin":63,"end":96}},"other_locations":[],"remediation_points":744000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `getGroups` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4f9fc97d0941125a5eb91818b85ac955","location":{"path":"app/assets/javascripts/groups/service/groups_service.js","lines":{"begin":9,"end":34}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 47 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"1cc8f54fcedff8d025db690e89c4d3bc","location":{"path":"app/assets/javascripts/clusters/clusters_bundle.js","lines":{"begin":24,"end":78}},"other_locations":[],"remediation_points":1128000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 45 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d34e04056341d2986626e42b9927e829","location":{"path":"app/assets/javascripts/clusters/stores/clusters_store.js","lines":{"begin":5,"end":51}},"other_locations":[],"remediation_points":1080000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initLayoutNav` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"858063cf6c5a28edd5390360940941f1","location":{"path":"app/assets/javascripts/layout_nav.js","lines":{"begin":12,"end":52}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `sidebarToggleClicked` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e95063093263d654532b1bda2499ae09","location":{"path":"app/assets/javascripts/right_sidebar.js","lines":{"begin":44,"end":69}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `toggleSidebar` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b4667aa48f617e84611c658bd9e6fd42","location":{"path":"app/assets/javascripts/right_sidebar.js","lines":{"begin":201,"end":218}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `message` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f561803cb2a47ffaeed70a1f967105da","location":{"path":"app/assets/javascripts/u2f/error.js","lines":{"begin":9,"end":21}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Function `issueTemplate` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"36dc94ada0224320b48f9dcddf971dd3","location":{"path":"app/assets/javascripts/api.js","lines":{"begin":223,"end":223}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"`Api` has 24 functions (exceeds 20 allowed). Consider refactoring.","fingerprint":"62c9c1d73c6c2f6f294b2c13a5c92e49","location":{"path":"app/assets/javascripts/api.js","lines":{"begin":5,"end":282}},"other_locations":[],"remediation_points":1600000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `validate` has 39 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8b07c343f7afe957592fac79043c9586","location":{"path":"app/assets/javascripts/new_branch_form.js","lines":{"begin":53,"end":94}},"other_locations":[],"remediation_points":936000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `labels_select.js` has 444 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"34830d2a928eacedb749d5c66c3637e9","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":1,"end":517}},"other_locations":[],"remediation_points":3993600,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `setDropdownData` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"99aee27c164d2c53ee3386f64239ebc9","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":466,"end":507}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 369 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ca542a5aefb3dbadb5e0a0100a94b863","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":16,"end":429}},"other_locations":[],"remediation_points":8856000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `data` has 41 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"00d06b2fdc398a9031e900f76184076f","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":133,"end":176}},"other_locations":[],"remediation_points":984000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `renderRow` has 50 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ef4d877e54496d7ad4da0e0a0b96d9aa","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":177,"end":235}},"other_locations":[],"remediation_points":1200000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `toggleLabel` has 31 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"35ba5b5dab8a50aa713a5ba60e1db7a1","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":242,"end":278}},"other_locations":[],"remediation_points":744000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `hidden` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"2dc1a005647c9e7e9f81e0eabeb9563d","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":294,"end":324}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `clicked` has 75 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f7fb23f1a88e5dc4c8430529946de3dc","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":327,"end":414}},"other_locations":[],"remediation_points":1800000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setDropdownData` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"955ed39a38f4f36c3ae0d23199b6b90d","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":466,"end":507}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"nested_control_flow","content":{"body":""},"description":"Avoid deeply nested control flow statements.","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":407,"end":412}},"other_locations":[],"remediation_points":450000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"cefdca8d12fc08aff6e7a861750e99d9"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":377,"end":377}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"1d311ac9ae722bf786749b2125e5cf47"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/labels_select.js","lines":{"begin":411,"end":411}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"7d42a3bbce082d2bbdc96d5eaa747454"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `storeEnvironments` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"70cce4c4dfcfa9c7fb820fc6dea68e9c","location":{"path":"app/assets/javascripts/environments/stores/environments_store.js","lines":{"begin":36,"end":71}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `storeEnvironments` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f5b13b9d301832978862f37bcc22708f","location":{"path":"app/assets/javascripts/environments/stores/environments_store.js","lines":{"begin":36,"end":71}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `created` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"139fddb1aa522eb2c141871eab765fc3","location":{"path":"app/assets/javascripts/environments/mixins/environments_mixin.js","lines":{"begin":142,"end":175}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 37 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"b4d0a598693b87337ee6b071a35c6da2","location":{"path":"app/assets/javascripts/issuable_context.js","lines":{"begin":7,"end":51}},"other_locations":[],"remediation_points":888000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `dropzoneInput` has 210 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"96984c8a7930ad755604f49c3edf6a2b","location":{"path":"app/assets/javascripts/dropzone_input.js","lines":{"begin":23,"end":290}},"other_locations":[],"remediation_points":5040000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"1ec26d8ddc2c55de770d1f8038f590f4","location":{"path":"app/assets/javascripts/zen_mode.js","lines":{"begin":39,"end":67}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `types.ADD_NEW_NOTE` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"026945d2d3de0e3f478ec55a7bb820f5","location":{"path":"app/assets/javascripts/notes/stores/mutations.js","lines":{"begin":7,"end":31}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `types.REMOVE_PLACEHOLDER_NOTES` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"26efdceb93879d8478e3c8a4f549bc22","location":{"path":"app/assets/javascripts/notes/stores/mutations.js","lines":{"begin":66,"end":85}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"`` has 22 functions (exceeds 20 allowed). Consider refactoring.","fingerprint":"75cd5c2d207814d4a7c7fbf69c709fd6","location":{"path":"app/assets/javascripts/notes/stores/mutations.js","lines":{"begin":6,"end":224}},"other_locations":[],"remediation_points":1400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `actions.js` has 271 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"9e2580af22da5d9875ac924fda6d0551","location":{"path":"app/assets/javascripts/notes/stores/actions.js","lines":{"begin":1,"end":337}},"other_locations":[],"remediation_points":1502400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `saveNote` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9dbdac686defafb20b3397ccb7105ba3","location":{"path":"app/assets/javascripts/notes/stores/actions.js","lines":{"begin":151,"end":226}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `getQuickActionText` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3019b0611c6febc7b696f0615d561d1b","location":{"path":"app/assets/javascripts/notes/stores/utils.js","lines":{"begin":7,"end":26}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `collapseSystemNotes` has 33 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"757d82e7c89224872a7bd7b269ed55c5","location":{"path":"app/assets/javascripts/notes/stores/collapse_utils.js","lines":{"begin":57,"end":105}},"other_locations":[],"remediation_points":792000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `unresolvedDiscussionsIdsByDiff` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"250bf3d3615586b7cdaecad2c187e0a0","location":{"path":"app/assets/javascripts/notes/stores/getters.js","lines":{"begin":117,"end":139}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `jumpToDiscussion` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ebbd3b5d75f27f2372eb1a56ca4d9206","location":{"path":"app/assets/javascripts/notes/mixins/discussion_navigation.js","lines":{"begin":5,"end":27}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `keydown` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1977a74c9fba9606480c93a63cb0d5c0","location":{"path":"app/assets/javascripts/droplab/plugins/filter.js","lines":{"begin":4,"end":47}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `keydown` has 33 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"6356352d7ef34cfddc4738ed0c5ebdbf","location":{"path":"app/assets/javascripts/droplab/plugins/filter.js","lines":{"begin":4,"end":47}},"other_locations":[],"remediation_points":792000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `trigger` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3ad4384530fc0032f4fafe9d12e7dc70","location":{"path":"app/assets/javascripts/droplab/plugins/ajax_filter.js","lines":{"begin":35,"end":71}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `_loadData` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"357b7070736cef3f382a2c3439a40d89","location":{"path":"app/assets/javascripts/droplab/plugins/ajax_filter.js","lines":{"begin":73,"end":92}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `trigger` has 35 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"bc97a87fa8ec1b132731256b23616434","location":{"path":"app/assets/javascripts/droplab/plugins/ajax_filter.js","lines":{"begin":35,"end":71}},"other_locations":[],"remediation_points":840000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `Keyboard` has a Cognitive Complexity of 49 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6b1d8f0930c09b5318307d0788ffca30","location":{"path":"app/assets/javascripts/droplab/keyboard.js","lines":{"begin":5,"end":111}},"other_locations":[],"remediation_points":4550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `Keyboard` has 96 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"a812687f4ed1c53045b473f0d6aa5cb9","location":{"path":"app/assets/javascripts/droplab/keyboard.js","lines":{"begin":5,"end":111}},"other_locations":[],"remediation_points":2304000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `keydown` has 35 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e7cbff360ff21f798bf424a899291302","location":{"path":"app/assets/javascripts/droplab/keyboard.js","lines":{"begin":70,"end":107}},"other_locations":[],"remediation_points":840000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `highlightHash` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2bd33253a3ff30d380e94d478d49a6e7","location":{"path":"app/assets/javascripts/line_highlighter.js","lines":{"begin":59,"end":82}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `highlightRange` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4258948c78c182b6db020222636c12bb","location":{"path":"app/assets/javascripts/line_highlighter.js","lines":{"begin":144,"end":157}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `checkElementsInView` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"08e5457bd1bcf2aa36f67185fb5ab932","location":{"path":"app/assets/javascripts/lazy_loader.js","lines":{"begin":53,"end":75}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `loadImage` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1e063810e532be97a06efabd3c56e4db","location":{"path":"app/assets/javascripts/lazy_loader.js","lines":{"begin":76,"end":94}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `constructor` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a3ef386977d1577238533b8b5352a499","location":{"path":"app/assets/javascripts/diff.js","lines":{"begin":14,"end":43}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `handleClickUnfold` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f82a7cc93be5e64d548469363a77a2fb","location":{"path":"app/assets/javascripts/diff.js","lines":{"begin":45,"end":79}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initMergeConflicts` has 81 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"dd259a46f5e703e8f722acdac619ad79","location":{"path":"app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js","lines":{"begin":12,"end":100}},"other_locations":[],"remediation_points":1944000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `merge_conflict_store.js` has 357 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"131a2e61cbfe7efc49cced2ffab3568b","location":{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":1,"end":436}},"other_locations":[],"remediation_points":2740800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setParallelLine` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f005034ab86d552698a2211ae91ae99a","location":{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":101,"end":139}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `isReadyToCommit` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"184e70a4c8de2226d5a4b197c147f2e8","location":{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":315,"end":351}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"nested_control_flow","content":{"body":""},"description":"Avoid deeply nested control flow statements.","location":{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":331,"end":333}},"other_locations":[],"remediation_points":450000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"78a238497c0c8acd6ddefbb67176f033"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `init` has 216 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"0c8af9cd2b45c1c2e6e24c4678a9c6fc","location":{"path":"app/assets/javascripts/milestone_select.js","lines":{"begin":23,"end":253}},"other_locations":[],"remediation_points":5184000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `clicked` has 91 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"c5e3838b8db3f11da2f72d9acaf52a17","location":{"path":"app/assets/javascripts/milestone_select.js","lines":{"begin":150,"end":250}},"other_locations":[],"remediation_points":2184000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/milestone_select.js","lines":{"begin":187,"end":187}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"9265cc21e5d1eb1bebd044e85db1bf79"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/milestone_select.js","lines":{"begin":220,"end":248}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"36ba0880a027f4448128be161cf33b26"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Function `createFlash` has 6 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"d8f8d96fc8578fb3f7f7bf875e0668a0","location":{"path":"app/assets/javascripts/flash.js","lines":{"begin":64,"end":69}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `textBuilder` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"877ac84192b385c963c08bb4ef960d3b","location":{"path":"app/assets/javascripts/reports/store/utils.js","lines":{"begin":10,"end":37}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 36 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e2f702de12038884b25d0ef63861bd2b","location":{"path":"app/assets/javascripts/ref_select_dropdown.js","lines":{"begin":4,"end":46}},"other_locations":[],"remediation_points":864000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setConfig` has 35 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"33b2d4c912bf8eb7a3c4b9c2d3302729","location":{"path":"app/assets/javascripts/close_reopen_report_toggle.js","lines":{"begin":57,"end":94}},"other_locations":[],"remediation_points":840000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `notes.js` has 1338 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"515dff155804bebfe20a78b3935600eb","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1,"end":1859}},"other_locations":[],"remediation_points":16867200,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `keydownNoteText` has a Cognitive Complexity of 24 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4a28bd8f572a9cb26ff49edf77e2d3a3","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":226,"end":278}},"other_locations":[],"remediation_points":2050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `renderNote` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bc0dc1a78ad18f837b6cead5d11a5ac2","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":403,"end":462}},"other_locations":[],"remediation_points":1550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `renderDiscussionNote` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.","fingerprint":"dbb0fd84250020e115dbb0554f3dfc2a","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":471,"end":538}},"other_locations":[],"remediation_points":1350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `toggleDiffNote` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ddb3cba4008791217e07933b09a14951","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1054,"end":1124}},"other_locations":[],"remediation_points":1350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `updateTargetButtons` has a Cognitive Complexity of 22 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4ea9fe607edc9cf98ef50a17584952c2","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1196,"end":1241}},"other_locations":[],"remediation_points":1850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `postComment` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cc8dd67d9f1598e83dc132f50b7f0726","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1604,"end":1796}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"`Notes` has 74 functions (exceeds 20 allowed). Consider refactoring.","fingerprint":"5363864915c583936ec6c63c5ed96689","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":48,"end":1856}},"other_locations":[],"remediation_points":6600000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 54 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"25812d53d4eb0f340c33a5a5ddca467a","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":59,"end":121}},"other_locations":[],"remediation_points":1296000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `addBinding` has 33 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"9b40ee2b94454885b3fd51a8bc139c1c","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":127,"end":180}},"other_locations":[],"remediation_points":792000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `keydownNoteText` has 48 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"85df701d158d32b25dca1c2039b6a3be","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":226,"end":278}},"other_locations":[],"remediation_points":1152000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `renderNote` has 50 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8dfb4b9b4d144d82422186651b46b3e4","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":403,"end":462}},"other_locations":[],"remediation_points":1200000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `renderDiscussionNote` has 53 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"52b66ad02e73c9b1c8848bd1affad7fc","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":471,"end":538}},"other_locations":[],"remediation_points":1272000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `removeNote` has 45 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8c1e3976ef9bdf79365857e0b8bcb5d8","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":848,"end":911}},"other_locations":[],"remediation_points":1080000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setupDiscussionNoteForm` has 35 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d1b7947a88bd1e4b3519e282320fbb38","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":956,"end":1009}},"other_locations":[],"remediation_points":840000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `toggleDiffNote` has 61 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"60ae737d4dc10fe0af6e670d2d4bc7e2","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1054,"end":1124}},"other_locations":[],"remediation_points":1464000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `updateTargetButtons` has 43 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8ccd0067a20bb8afcaf3317b434b1ece","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1196,"end":1241}},"other_locations":[],"remediation_points":1032000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `createPlaceholderNote` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ddcd3bc4735c7d763b0fe5da55217278","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1526,"end":1566}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `postComment` has 143 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"fcdd1766ceaaa90c770bddfe75d120cd","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1604,"end":1796}},"other_locations":[],"remediation_points":3432000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `updateComment` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"7c508bf1571f9f4d795de4cb8ef1930a","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1811,"end":1855}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":264,"end":264}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"cd548cc7c1ebbb2263b1d2ecdb94a5dd"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":272,"end":272}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"cd548cc7c1ebbb2263b1d2ecdb94a5dd"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":275,"end":275}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"25fb735695be215146eaf2300412aae0"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `itemClicked` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"86a06125a02d35c7e121952daf7f0f08","location":{"path":"app/assets/javascripts/filtered_search/dropdown_hint.js","lines":{"begin":23,"end":60}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `itemClicked` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e7751795d821a7afa57ab25dbd658ee4","location":{"path":"app/assets/javascripts/filtered_search/dropdown_hint.js","lines":{"begin":23,"end":60}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `filterHint` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"22102abbd93e4d53a0798ecf7e058512","location":{"path":"app/assets/javascripts/filtered_search/dropdown_utils.js","lines":{"begin":117,"end":140}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `getSearchQuery` has 40 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"26e123d9f4a95bfa34a59f42db0ae260","location":{"path":"app/assets/javascripts/filtered_search/dropdown_utils.js","lines":{"begin":164,"end":213}},"other_locations":[],"remediation_points":960000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `processTokens` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"28cb3a1b7fa1a51cd968e9016415efea","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_tokenizer.js","lines":{"begin":4,"end":51}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `processTokens` has 36 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"287e8c4963cc8869358280d31e4d4005","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_tokenizer.js","lines":{"begin":4,"end":51}},"other_locations":[],"remediation_points":864000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `loadDropdown` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"31af8cc264fa78512d238c04106208fe","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js","lines":{"begin":202,"end":219}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `setDropdown` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6dda613591636a58b06a298d0980498f","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js","lines":{"begin":221,"end":243}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setupMapping` has 55 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f12b787fff78b0b8986506eb90b1997c","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js","lines":{"begin":50,"end":108}},"other_locations":[],"remediation_points":1320000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `load` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"90c65f9cea258055f67defcb61f63abb","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js","lines":{"begin":165,"end":200}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `filtered_search_manager.js` has 514 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"45105bf38088a343a5a9c0e1ca76840e","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":1,"end":645}},"other_locations":[],"remediation_points":5001600,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `setup` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8c08d2326cb1eb811c4329b3ee92fc04","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":58,"end":106}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `checkForEnter` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2056e10cb965de92a331a6181618ff7f","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":236,"end":265}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `handleInputVisualToken` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3ef6573dc182d159eb13ddd70be42653","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":398,"end":438}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"`FilteredSearchManager` has 32 functions (exceeds 20 allowed). Consider refactoring.","fingerprint":"dee04c9127382346c9edead8ceb49788","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":20,"end":644}},"other_locations":[],"remediation_points":2400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setup` has 38 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"fc525f4373639fe1e5e079259d587918","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":58,"end":106}},"other_locations":[],"remediation_points":912000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `bindEvents` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"0036874f7fdfa7b481bccec4adce2fd9","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":145,"end":182}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `handleInputVisualToken` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"105fd464c05f365ea496107488c4d048","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":398,"end":438}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `loadSearchParamsFromURL` has 63 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"dc0a70cc2480e01d46d52df00f42980b","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":463,"end":543}},"other_locations":[],"remediation_points":1512000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `search` has 36 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8d31d46c10a7c7b27cba3e79c2c7e684","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":558,"end":607}},"other_locations":[],"remediation_points":864000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"nested_control_flow","content":{"body":""},"description":"Avoid deeply nested control flow statements.","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":524,"end":529}},"other_locations":[],"remediation_points":450000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"b4fcaa9a90b83251c167ce6bd363ead0"},{"categories":["Complexity"],"check_name":"nested_control_flow","content":{"body":""},"description":"Avoid deeply nested control flow statements.","location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":530,"end":533}},"other_locations":[],"remediation_points":450000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"b9acc2eaf5ae1dd8b6f24ac70e97ace6"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `users_select.js` has 594 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"9e6ca5bcc402f0dae9a9869dd46e4e77","location":{"path":"app/assets/javascripts/users_select.js","lines":{"begin":1,"end":701}},"other_locations":[],"remediation_points":6153600,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `UsersSelect` has 524 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"b4438d98e020e34865089e812790a72e","location":{"path":"app/assets/javascripts/users_select.js","lines":{"begin":14,"end":628}},"other_locations":[],"remediation_points":12576000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `processData` has 88 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"9e7c9e224218cce1f434b0a0ff4616c4","location":{"path":"app/assets/javascripts/users_select.js","lines":{"begin":221,"end":327}},"other_locations":[],"remediation_points":2112000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `clicked` has 64 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"19cb3539002fa7a4aa2abf730fc550bc","location":{"path":"app/assets/javascripts/users_select.js","lines":{"begin":381,"end":468}},"other_locations":[],"remediation_points":1536000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `renderRow` has 31 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"b67cb57917ef04a8b05e4be075bf6c0b","location":{"path":"app/assets/javascripts/users_select.js","lines":{"begin":493,"end":531}},"other_locations":[],"remediation_points":744000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `query` has 48 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"447defa81cda25e0ae3f708d8cb7f927","location":{"path":"app/assets/javascripts/users_select.js","lines":{"begin":553,"end":604}},"other_locations":[],"remediation_points":1152000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `toggleCollapsed` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"01ddc746dbdfa4103552d4d09bda67bd","location":{"path":"app/assets/javascripts/image_diff/helpers/dom_helper.js","lines":{"begin":27,"end":45}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `addBadge` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"5cb8fbd4ee1e42eb778a29cd02efc8f5","location":{"path":"app/assets/javascripts/image_diff/image_diff.js","lines":{"begin":84,"end":116}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 45 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"1579b1a9e97ea98e6348df70287ccd85","location":{"path":"app/assets/javascripts/job.js","lines":{"begin":13,"end":70}},"other_locations":[],"remediation_points":1080000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `getBuildTrace` has 55 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"0990625843278c1e0d27866b6e51ec9b","location":{"path":"app/assets/javascripts/job.js","lines":{"begin":96,"end":163}},"other_locations":[],"remediation_points":1320000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setVisibilityOptions` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"bd793c0b96469cb6904241d8beea60c0","location":{"path":"app/assets/javascripts/project_visibility.js","lines":{"begin":3,"end":35}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `restore` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ebd9e5c02ce67b5fecb0e9a610fa01a3","location":{"path":"app/assets/javascripts/autosave.js","lines":{"begin":19,"end":37}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `populateActiveMetrics` has 33 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"0808d7392776f800f01eb00247cb49fc","location":{"path":"app/assets/javascripts/prometheus_metrics/prometheus_metrics.js","lines":{"begin":63,"end":100}},"other_locations":[],"remediation_points":792000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `loadActiveMetrics` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d2f56d70d330df4e004e4e730ec9bd7d","location":{"path":"app/assets/javascripts/prometheus_metrics/prometheus_metrics.js","lines":{"begin":102,"end":130}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `adminInit` has 37 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"cb6b998e81923ba316eb986d14f16ef7","location":{"path":"app/assets/javascripts/pages/admin/admin.js","lines":{"begin":14,"end":60}},"other_locations":[],"remediation_points":888000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `renderState` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d23e829e807e7cfa7fa60a5bd8bd2a4f","location":{"path":"app/assets/javascripts/pages/sessions/new/username_validator.js","lines":{"begin":49,"end":72}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/pages/sessions/new/username_validator.js","lines":{"begin":70,"end":70}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"59ee4d34d9113510c329a2b264aae308"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `showTab` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2db6bb70760f9a2493d374e830395ef3","location":{"path":"app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js","lines":{"begin":34,"end":47}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 52 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"364739acddb57495be3b94860555f2ae","location":{"path":"app/assets/javascripts/pages/projects/project.js","lines":{"begin":13,"end":72}},"other_locations":[],"remediation_points":1248000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initRefSwitcher` has 63 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e62a2b92cc28ef2e2c5cc5e391c1b3c1","location":{"path":"app/assets/javascripts/pages/projects/project.js","lines":{"begin":83,"end":155}},"other_locations":[],"remediation_points":1512000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initLabelIndex` has 72 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d6cbf4de48d122212aa6790bce385c52","location":{"path":"app/assets/javascripts/pages/projects/labels/index/index.js","lines":{"begin":9,"end":91}},"other_locations":[],"remediation_points":1728000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `stat_graph_contributors_graph.js` has 260 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"d70980224b971643c65164d7599d8306","location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":1,"end":315}},"other_locations":[],"remediation_points":1344000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `ContributorsGraph` has 68 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"c5ffd1e9b7f7eb20d76540029db76ea5","location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":19,"end":107}},"other_locations":[],"remediation_points":1632000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `ContributorsMasterGraph` has 98 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"c90cea5a64860bd8ebc11adfe43f966f","location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":109,"end":225}},"other_locations":[],"remediation_points":2352000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `ContributorsAuthorGraph` has 75 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"cb2ba67ac3c7b0319408d414f7ab4a89","location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":227,"end":314}},"other_locations":[],"remediation_points":1800000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `parse_log` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"54a68f13ca92482f197897caa4dde8c6","location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js","lines":{"begin":5,"end":31}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `renderSidebar` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7a8652d503fb546ee9566f5bf3f473ac","location":{"path":"app/assets/javascripts/pages/projects/wikis/wikis.js","lines":{"begin":54,"end":66}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `activity_calendar.js` has 257 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"c8cf6370baf6d39528465c33a636bdaa","location":{"path":"app/assets/javascripts/pages/users/activity_calendar.js","lines":{"begin":1,"end":300}},"other_locations":[],"remediation_points":1300800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `constructor` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"12e8c60549aefe50a5602474054d85e9","location":{"path":"app/assets/javascripts/pages/users/activity_calendar.js","lines":{"begin":46,"end":115}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 49 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"70826affaa552c98140c396bd20b2321","location":{"path":"app/assets/javascripts/pages/users/activity_calendar.js","lines":{"begin":46,"end":115}},"other_locations":[],"remediation_points":1176000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `renderDays` has 37 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"b25511a5a67d72f5494bf518b57f8945","location":{"path":"app/assets/javascripts/pages/users/activity_calendar.js","lines":{"begin":144,"end":182}},"other_locations":[],"remediation_points":888000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `renderKey` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f239dc6410f68cc561519c650b54fe79","location":{"path":"app/assets/javascripts/pages/users/activity_calendar.js","lines":{"begin":226,"end":257}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 65 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"910e3f36c66a265e42a8eaf26a167c4b","location":{"path":"app/assets/javascripts/pages/search/show/search.js","lines":{"begin":6,"end":77}},"other_locations":[],"remediation_points":1560000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `showPreview` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"1721f777313e23b7b202fa7491480968","location":{"path":"app/assets/javascripts/behaviors/preview_markdown.js","lines":{"begin":29,"end":64}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `copy_as_gfm.js` has 400 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"ac03b65c923edc62a2698fa8d47bed20","location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":1,"end":510}},"other_locations":[],"remediation_points":3360000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `transformCodeSelection` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c2c7d04215c2bd2989380d4ec16d2fdd","location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":392,"end":428}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `nodeToGFM` has a Cognitive Complexity of 20 (exceeds 5 allowed). Consider refactoring.","fingerprint":"203624a0d90ac2910cd61598bd1ba9c4","location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":430,"end":473}},"other_locations":[],"remediation_points":1650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"`gfmRules` has 23 functions (exceeds 20 allowed). Consider refactoring.","fingerprint":"4cef7679aa1f25cb0fb0333b4dfea466","location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":169,"end":306}},"other_locations":[],"remediation_points":1500000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `transformCodeSelection` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"cc79eaa8e95f40163769d11480f6ca8c","location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":392,"end":428}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `nodeToGFM` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"abacf74acfb9ae1429676cb952d5250f","location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":430,"end":473}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":472,"end":472}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"4dc32f63393b2dba3bb2b4186a5a9d3c"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initPageShortcuts` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"7e91b97abbe33722ed743c916435f0ab","location":{"path":"app/assets/javascripts/behaviors/shortcuts.js","lines":{"begin":3,"end":35}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"3992dd4b39964f74489f5037465f0968","location":{"path":"app/assets/javascripts/behaviors/shortcuts/shortcuts.js","lines":{"begin":18,"end":51}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `updateTopState` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4115079d252dabe584b1839baffa5696","location":{"path":"app/assets/javascripts/issue.js","lines":{"begin":46,"end":74}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `bindEvents` has 99 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d911289330106e823f8b66a27a6de9a8","location":{"path":"app/assets/javascripts/projects/project_new.js","lines":{"begin":38,"end":154}},"other_locations":[],"remediation_points":2376000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `chooseTemplate` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"38d61f9d4b3748a9e5ab863d54f4ccf8","location":{"path":"app/assets/javascripts/projects/project_new.js","lines":{"begin":98,"end":130}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `search_autocomplete.js` has 417 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"92cc9af55c3a4ab73da8e1f0d3edd029","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":1,"end":506}},"other_locations":[],"remediation_points":3604800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `getData` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fc8158cb371adf854571129bd363b7ef","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":149,"end":251}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `getCategoryContents` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d8c7fe125446a794240cdecc04543593","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":253,"end":305}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `onSearchInputKeyUp` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4f647cc28b710d6de8a6b35f3f79f3ce","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":348,"end":382}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `onClick` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b118715fdcf7b3e7ac25f69bb1baa423","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":445,"end":458}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"`SearchAutocomplete` has 28 functions (exceeds 20 allowed). Consider refactoring.","fingerprint":"4afabdab2d1c2b911d6a1c563638e8e6","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":71,"end":501}},"other_locations":[],"remediation_points":2000000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setSearchOptions` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ba3145218ad79282dc1d42a52ae16802","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":32,"end":69}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"27f6673ed668afd5ac9f3fd3c02a5e40","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":72,"end":103}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `getData` has 86 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"a21aceb13c361e2e3e0299baa560bed5","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":149,"end":251}},"other_locations":[],"remediation_points":2064000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `getCategoryContents` has 45 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"415e6e1a246f7b01199ed828bdebb278","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":253,"end":305}},"other_locations":[],"remediation_points":1080000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `onSearchInputKeyUp` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"438f5589be7d3c9865113320e8a85fc4","location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":348,"end":382}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `highlighter` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"034f805c8a510c0c46e35c2e4fb8d329","location":{"path":"app/assets/javascripts/project_find_file.js","lines":{"begin":10,"end":32}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `selectRow` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b2917d9725004429f0a4319beac0822e","location":{"path":"app/assets/javascripts/project_find_file.js","lines":{"begin":123,"end":143}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `constructor` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1b590709d469cb1c8c28f467e53d2154","location":{"path":"app/assets/javascripts/namespace_select.js","lines":{"begin":8,"end":58}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 48 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"af0c07eddd69b8e22c3e13c7474122a1","location":{"path":"app/assets/javascripts/namespace_select.js","lines":{"begin":8,"end":58}},"other_locations":[],"remediation_points":1152000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `toggleDiff` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"098b6e689349244e7adddfcad90974a3","location":{"path":"app/assets/javascripts/single_file_diff.js","lines":{"begin":41,"end":62}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `onSaveClicked` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"18922f177ff6315e508e19e5d7a6cd4d","location":{"path":"app/assets/javascripts/ci_variable_list/ajax_variable_list.js","lines":{"begin":53,"end":90}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"cf8b6d0bf24fc15e2c0e87e1c6b6a636","location":{"path":"app/assets/javascripts/ci_variable_list/ci_variable_list.js","lines":{"begin":19,"end":62}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `jumpToNextUnresolvedDiscussion` has a Cognitive Complexity of 54 (exceeds 5 allowed). Consider refactoring.","fingerprint":"792eaacd2770c50c0a42bce535c7bee9","location":{"path":"app/assets/javascripts/diff_notes/components/jump_to_discussion.js","lines":{"begin":61,"end":208}},"other_locations":[],"remediation_points":5050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `jumpToNextUnresolvedDiscussion` has 102 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"784d361fd1ec4da2fc3be0e85d1e354e","location":{"path":"app/assets/javascripts/diff_notes/components/jump_to_discussion.js","lines":{"begin":61,"end":208}},"other_locations":[],"remediation_points":2448000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `buttonText` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5b0d7c1b1146fa7930c3c14fef72db27","location":{"path":"app/assets/javascripts/diff_notes/components/comment_resolve_btn.js","lines":{"begin":31,"end":45}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `diffNotesCompileComponents` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ec443ad0f520ec34248990713c0f0b5a","location":{"path":"app/assets/javascripts/diff_notes/diff_notes_bundle.js","lines":{"begin":30,"end":68}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Function `queryTimeSeries` has 7 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"01a425e5bd2ad64d006f35854f477ad0","location":{"path":"app/assets/javascripts/monitoring/utils/multiple_time_series.js","lines":{"begin":33,"end":33}},"other_locations":[],"remediation_points":525000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `queryTimeSeries` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f1015e96e281a9a77bdaeeba619aa36b","location":{"path":"app/assets/javascripts/monitoring/utils/multiple_time_series.js","lines":{"begin":33,"end":165}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `queryTimeSeries` has 110 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"c99f8cbe0230e2f1d48ff4478f2117c4","location":{"path":"app/assets/javascripts/monitoring/utils/multiple_time_series.js","lines":{"begin":33,"end":165}},"other_locations":[],"remediation_points":2640000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `timeScaleFormat` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f52e997312984f599f152d3260bf8654","location":{"path":"app/assets/javascripts/monitoring/utils/date_time_formatters.js","lines":{"begin":22,"end":42}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"complex_logic","content":{"body":""},"description":"Consider simplifying this complex logical expression.","location":{"path":"app/assets/javascripts/diffs/store/mutations.js","lines":{"begin":95,"end":134}},"other_locations":[],"remediation_points":800000,"severity":"critical","type":"issue","engine_name":"structure","fingerprint":"531fa4647bfb14d1172613e86257dd28"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `types.SET_LINE_DISCUSSIONS_FOR_FILE` has a Cognitive Complexity of 16 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f6d1e6ad89a6aec9b87ec051dfa0099d","location":{"path":"app/assets/javascripts/diffs/store/mutations.js","lines":{"begin":88,"end":135}},"other_locations":[],"remediation_points":1250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `types.REMOVE_LINE_DISCUSSIONS_FOR_FILE` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"967d987657c3659898f377b3d07be15b","location":{"path":"app/assets/javascripts/diffs/store/mutations.js","lines":{"begin":137,"end":165}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `types.SET_LINE_DISCUSSIONS_FOR_FILE` has 43 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"08263cf95339fef038c9b82b56e70844","location":{"path":"app/assets/javascripts/diffs/store/mutations.js","lines":{"begin":88,"end":135}},"other_locations":[],"remediation_points":1032000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `startRenderDiffsQueue` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9d28a6db58513b7ea1157b081952903c","location":{"path":"app/assets/javascripts/diffs/store/actions.js","lines":{"begin":58,"end":83}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `addLineReferences` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"782481c579d96128e5d0793c04d6113b","location":{"path":"app/assets/javascripts/diffs/store/utils.js","lines":{"begin":110,"end":145}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `prepareDiffData` has a Cognitive Complexity of 20 (exceeds 5 allowed). Consider refactoring.","fingerprint":"69f60a55e02467d84573c99a4caa96e1","location":{"path":"app/assets/javascripts/diffs/store/utils.js","lines":{"begin":191,"end":224}},"other_locations":[],"remediation_points":1650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `getNoteFormData` has 45 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"2408381b663271af2ce36e4cfec0ef64","location":{"path":"app/assets/javascripts/diffs/store/utils.js","lines":{"begin":28,"end":77}},"other_locations":[],"remediation_points":1080000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `addLineReferences` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"9e04f00e197ce4b77a3fa65c55e66cb1","location":{"path":"app/assets/javascripts/diffs/store/utils.js","lines":{"begin":110,"end":145}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `prepareDiffData` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"4fa4b2e2a939b7a261a1e47aea3b124e","location":{"path":"app/assets/javascripts/diffs/store/utils.js","lines":{"begin":191,"end":224}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `shouldRenderParallelCommentRow` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cf200732380b3f0a2a2993c8cb3d3a71","location":{"path":"app/assets/javascripts/diffs/store/getters.js","lines":{"begin":75,"end":97}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initDiffsApp` has 33 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"37689c9906180d81c8f636a1285361fa","location":{"path":"app/assets/javascripts/diffs/index.js","lines":{"begin":6,"end":41}},"other_locations":[],"remediation_points":792000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `create_merge_request_dropdown.js` has 391 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"efe0e6e60956f829c50aea15ff89a81c","location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":1,"end":489}},"other_locations":[],"remediation_points":3230400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `onChangeInput` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"51e3d7e3f8979e2acacf219907cfae33","location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":278,"end":322}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `updateInputState` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"15a5141561c69faaf772cc24838b9882","location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":433,"end":472}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"`CreateMergeRequestDropdown` has 32 functions (exceeds 20 allowed). Consider refactoring.","fingerprint":"6d33b610bd5704267ec05a9b7b5e44e0","location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":15,"end":488}},"other_locations":[],"remediation_points":2400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 38 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"9408c08016dc994d8ab6445d06b9e1ba","location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":16,"end":61}},"other_locations":[],"remediation_points":912000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `onChangeInput` has 33 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e5627e01c10e2d6e1299f91b38024a0a","location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":278,"end":322}},"other_locations":[],"remediation_points":792000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `updateInputState` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"5126386129e905b8737efa7b7aadb02e","location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":433,"end":472}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":321,"end":321}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"8643526d248036a3735dc9a6e2e57920"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `addToImport` has 48 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f5c4a1a17a8903f8c6dd885142d0e5a7","location":{"path":"app/assets/javascripts/importer_status.js","lines":{"begin":33,"end":92}},"other_locations":[],"remediation_points":1152000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `autoUpdate` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"86f5ee9b3a803d7476f4c94f06eeb20f","location":{"path":"app/assets/javascripts/importer_status.js","lines":{"begin":94,"end":123}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `deviseState` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cc96fa07083c1de787a6bff9f39300d8","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":3,"end":34}},"other_locations":[],"remediation_points":1350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `deviseState` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"b9fdc968681b9a4d1a00bdaebcf5513e","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":3,"end":34}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":13,"end":13}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"7e01335e6a48fbcffe9b977a5893e0c1"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":15,"end":15}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"06b18956af744e4ba0818ef408ae519a"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":17,"end":17}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"b6db36d200c40a47864a6c82d74ecdd9"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":19,"end":19}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"94cbbf89dae775243077f30c7139777f"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":21,"end":21}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"407c4389388a40459b3a50e566ec4695"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":23,"end":23}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"c9317c489fdab4a76a50dbc5cebcfa17"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":25,"end":25}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"d69b63a794f4784245d6a949c148e1c8"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":27,"end":27}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"766dbcf000fa7d4e2636539267ea8cee"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":29,"end":29}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"9c3dd1c5c99c0b2e367eaf0f6c483688"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":31,"end":31}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"9735ac3532df3e31eea793eb471bcd3e"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js","lines":{"begin":33,"end":33}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"6a3a4a85c8ee79ec2d8e4ea2cf165674"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `setData` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c334ad6c327b60fc52e19d4b1a441be0","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js","lines":{"begin":14,"end":114}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setData` has 88 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"590c6f1bfea578d864bdc64a2fa9a1da","location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js","lines":{"begin":14,"end":114}},"other_locations":[],"remediation_points":2112000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `groupsSelect` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6d30e631abef41c7858e8c2b472ab41b","location":{"path":"app/assets/javascripts/groups_select.js","lines":{"begin":6,"end":88}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `groupsSelect` has 74 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ced5ccd57fcd0e841cc27868cc850186","location":{"path":"app/assets/javascripts/groups_select.js","lines":{"begin":6,"end":88}},"other_locations":[],"remediation_points":1776000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setAjaxGroupsSelect2` has 71 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"cb6c68e362058d81fc462714f431d13d","location":{"path":"app/assets/javascripts/groups_select.js","lines":{"begin":9,"end":87}},"other_locations":[],"remediation_points":1704000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `setConfig` has 31 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"93dedf1db90bd5e014f609731004784e","location":{"path":"app/assets/javascripts/comment_type_toggle.js","lines":{"begin":25,"end":60}},"other_locations":[],"remediation_points":744000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `memberExpirationDate` has 33 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"93c026069e6f3fae025e1bf3bf480e0b","location":{"path":"app/assets/javascripts/member_expiration_date.js","lines":{"begin":10,"end":54}},"other_locations":[],"remediation_points":792000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `merge_request_tabs.js` has 311 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"91dcee71900ea33c229c8f115d145f57","location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":1,"end":477}},"other_locations":[],"remediation_points":2078400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `constructor` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2c515fc51ceabbbd6bcacbadf7b409c8","location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":71,"end":122}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `tabShown` has a Cognitive Complexity of 29 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ca9c966746914ecd639f907aed00306a","location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":159,"end":223}},"other_locations":[],"remediation_points":2550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 43 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"5e286159c60ca68824ec344d5dec0145","location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":71,"end":122}},"other_locations":[],"remediation_points":1032000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `tabShown` has 56 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"5752347dcb1fd9940bd4bc3d21abd442","location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":159,"end":223}},"other_locations":[],"remediation_points":1344000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `loadDiff` has 52 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"3eedd69a3c93529e5939b19ecf1b3e32","location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":331,"end":401}},"other_locations":[],"remediation_points":1248000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `toggleLabelPriority` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"17bb8b4767aa6ee7a9db29cd4297dfd1","location":{"path":"app/assets/javascripts/label_manager.js","lines":{"begin":53,"end":94}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `toggleLabelPriority` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ac24663bc55eac3c9635bccf3a68dc5a","location":{"path":"app/assets/javascripts/label_manager.js","lines":{"begin":53,"end":94}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"4cd0496900d30272b32ee2e3adf188ae","location":{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":11,"end":46}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `addIssue` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.","fingerprint":"70f0f8ea0547b7001e920932e4980ddc","location":{"path":"app/assets/javascripts/boards/models/list.js","lines":{"begin":164,"end":200}},"other_locations":[],"remediation_points":1550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `addIssue` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"7f1eced6b6dca9d960756de9b56ac3c4","location":{"path":"app/assets/javascripts/boards/models/list.js","lines":{"begin":164,"end":200}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Function `moveIssueInList` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"3726eea384e1d9e6d6d8eebb989b0a55","location":{"path":"app/assets/javascripts/boards/stores/boards_store.js","lines":{"begin":152,"end":152}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `moveIssueToList` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cc191b6dd17cde39e7dd162fd47e19c7","location":{"path":"app/assets/javascripts/boards/stores/boards_store.js","lines":{"begin":100,"end":144}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `moveIssueToList` has 38 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"0501fa18f105e7c808db7348aee50cb4","location":{"path":"app/assets/javascripts/boards/stores/boards_store.js","lines":{"begin":100,"end":144}},"other_locations":[],"remediation_points":912000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `newListDropdownInit` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8f897d86e7796b5b3d49f700124b5adb","location":{"path":"app/assets/javascripts/boards/components/new_list_dropdown.js","lines":{"begin":26,"end":82}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `newListDropdownInit` has 51 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f73b08dfbb16f388d03764aefc71b663","location":{"path":"app/assets/javascripts/boards/components/new_list_dropdown.js","lines":{"begin":26,"end":82}},"other_locations":[],"remediation_points":1224000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `projectSelect` has 74 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8ccd46aac4a6745374730b2c4f0f1a7f","location":{"path":"app/assets/javascripts/project_select.js","lines":{"begin":7,"end":87}},"other_locations":[],"remediation_points":1776000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `query` has 36 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8facf3a4b06c65c72b401a6ae241812e","location":{"path":"app/assets/javascripts/project_select.js","lines":{"begin":27,"end":64}},"other_locations":[],"remediation_points":864000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `swipe` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"cd344682f57e11e94307c0ae71e31b21","location":{"path":"app/assets/javascripts/commit/image_file.js","lines":{"begin":118,"end":153}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `onion-skin` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d98db2ac5d0ba5352c5501d8e616908a","location":{"path":"app/assets/javascripts/commit/image_file.js","lines":{"begin":154,"end":193}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `mergeUrlParams` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9586f62e5526e65ea5dea846d2d34814","location":{"path":"app/assets/javascripts/lib/utils/url_utility.js","lines":{"begin":19,"end":41}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `common_utils.js` has 421 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"5b2e1f175792cddc3122cb26f6f2901b","location":{"path":"app/assets/javascripts/lib/utils/common_utils.js","lines":{"begin":1,"end":647}},"other_locations":[],"remediation_points":3662400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `handleLocationHash` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"73cd83cbd5164515c6be6e51ced09b94","location":{"path":"app/assets/javascripts/lib/utils/common_utils.js","lines":{"begin":79,"end":112}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `urlParamsToObject` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"87d0167946e02e230645b1a8b4416926","location":{"path":"app/assets/javascripts/lib/utils/common_utils.js","lines":{"begin":152,"end":175}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `createOverlayIcon` has 40 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"1195653ae898b98df0d276acb1429b55","location":{"path":"app/assets/javascripts/lib/utils/common_utils.js","lines":{"begin":458,"end":505}},"other_locations":[],"remediation_points":960000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `onload` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d3e5a07a00d95bb2f853dee0c1c50e98","location":{"path":"app/assets/javascripts/lib/utils/common_utils.js","lines":{"begin":462,"end":502}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `isSticky` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b5704d88401ccd1910b07573165064f6","location":{"path":"app/assets/javascripts/lib/utils/sticky.js","lines":{"begin":10,"end":31}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Function `insertMarkdownText` has 6 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"1ff368be1929703177fe923eb38be8a2","location":{"path":"app/assets/javascripts/lib/utils/text_markdown.js","lines":{"begin":54,"end":54}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `moveCursor` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"78da0046365fa4a2a913735a7884c4fe","location":{"path":"app/assets/javascripts/lib/utils/text_markdown.js","lines":{"begin":34,"end":52}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `insertMarkdownText` has a Cognitive Complexity of 18 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9c17d7ff9d0dfd043e0ac8b46e612742","location":{"path":"app/assets/javascripts/lib/utils/text_markdown.js","lines":{"begin":54,"end":111}},"other_locations":[],"remediation_points":1450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `insertMarkdownText` has 43 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f38a0d7dccba49d8195065ab71f8ef39","location":{"path":"app/assets/javascripts/lib/utils/text_markdown.js","lines":{"begin":54,"end":111}},"other_locations":[],"remediation_points":1032000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `toggleScroll` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a1904c8e94b053af5f2371dece57951f","location":{"path":"app/assets/javascripts/lib/utils/logoutput_behaviours.js","lines":{"begin":14,"end":41}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `getMonthNames` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"5630d70a3018ea651454e11d959c7e26","location":{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":16,"end":47}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `getTimeago` has 42 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"915cddbb51190cedb7c5488e6d3f715a","location":{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":77,"end":122}},"other_locations":[],"remediation_points":1008000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `dateInWords` has 36 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"35c8c58c18a9af9c0ee997ae5d4b8e48","location":{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":201,"end":243}},"other_locations":[],"remediation_points":864000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initKeyNav` has 37 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"90bcaab65ebc8665cd94e8e6286ad7d3","location":{"path":"app/assets/javascripts/tree.js","lines":{"begin":28,"end":66}},"other_locations":[],"remediation_points":888000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `generateUnicodeSupportMap` has 47 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"3235c3b88f6c553ff280eac9faec19fa","location":{"path":"app/assets/javascripts/emoji/support/unicode_support_map.js","lines":{"begin":77,"end":141}},"other_locations":[],"remediation_points":1128000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `getUnicodeSupportMap` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d9342f9326c59d8e19277db8a2cb8500","location":{"path":"app/assets/javascripts/emoji/support/unicode_support_map.js","lines":{"begin":143,"end":178}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `glEmojiTag` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"580f88e3583b6c310ded6fbad9390305","location":{"path":"app/assets/javascripts/emoji/index.js","lines":{"begin":69,"end":102}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initDropdown` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"301e9b71ddb9969243fc57daa227b33b","location":{"path":"app/assets/javascripts/sidebar/lib/sidebar_move_issue.js","lines":{"begin":27,"end":59}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `branch_graph.js` has 307 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"8f961e52cddbca0cb2dd3290c7063853","location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":1,"end":352}},"other_locations":[],"remediation_points":2020800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `buildGraph` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"18ed687a5ecc08e272ec3f5a1e45ba85","location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":103,"end":139}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `renderPartialGraph` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"790548e7db64ebbadd07868d6c20245b","location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":141,"end":171}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `appendLabel` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"918265d9fa262a2ea6ddda4b96f04407","location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":211,"end":246}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `drawLines` has 39 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"df364db884043c3c81e753fb9a58ee90","location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":286,"end":333}},"other_locations":[],"remediation_points":936000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `testWrap` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"65093271e08f9838db54de85e17d7c79","location":{"path":"app/assets/javascripts/network/raphael.js","lines":{"begin":37,"end":72}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `commitTooltip` has 31 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"c2753572072946d03cae221981b2a147","location":{"path":"app/assets/javascripts/network/raphael.js","lines":{"begin":3,"end":35}},"other_locations":[],"remediation_points":744000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `testWrap` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"3ac8fefe6574f998844a591c4dff399c","location":{"path":"app/assets/javascripts/network/raphael.js","lines":{"begin":37,"end":72}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `types.TOGGLE_LOADING` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"41230e2a140c3c519f16d17067a948c0","location":{"path":"app/assets/javascripts/ide/stores/mutations.js","lines":{"begin":14,"end":24}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `types.RENAME_ENTRY` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"65c7f9e9aa2de7fa4e24860dbded9820","location":{"path":"app/assets/javascripts/ide/stores/mutations.js","lines":{"begin":214,"end":263}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"`` has 26 functions (exceeds 20 allowed). Consider refactoring.","fingerprint":"ee3f6d8624f678d9741d386c4252f0fc","location":{"path":"app/assets/javascripts/ide/stores/mutations.js","lines":{"begin":10,"end":269}},"other_locations":[],"remediation_points":1800000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `types.RENAME_ENTRY` has 40 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"98f116d65b4aa0e565234027efee827f","location":{"path":"app/assets/javascripts/ide/stores/mutations.js","lines":{"begin":214,"end":263}},"other_locations":[],"remediation_points":960000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `types.SET_FILE_RAW_DATA` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6460bb9792e8eedb0b1e558f229e6ffa","location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":53,"end":75}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `types.DISCARD_FILE_CHANGES` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"aa62953384fc873bd8bddabed1341a3a","location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":128,"end":154}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `closeFile` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ca626095e258a497e9cf9c1fa4b7537e","location":{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":10,"end":40}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `createCommitPayload` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ead12c6a7c7c105251c94c6af2fda7b0","location":{"path":"app/assets/javascripts/ide/stores/utils.js","lines":{"begin":133,"end":145}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `decorateData` has 44 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f8abacda37ac495ce54d8ce69e488724","location":{"path":"app/assets/javascripts/ide/stores/utils.js","lines":{"begin":55,"end":101}},"other_locations":[],"remediation_points":1056000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this function.","location":{"path":"app/assets/javascripts/ide/stores/utils.js","lines":{"begin":158,"end":158}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"75e7e39f5b8ea0aadff2470a9b44ca68"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `entries` has 67 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"1a9caa4d3fce207f88b2afafe14a1df0","location":{"path":"app/assets/javascripts/ide/stores/workers/files_decorator_worker.js","lines":{"begin":11,"end":91}},"other_locations":[],"remediation_points":1608000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Function `types.RECEIVE_LASTEST_PIPELINE_SUCCESS` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ac7ea082c786419244a234de0970ebc9","location":{"path":"app/assets/javascripts/ide/stores/modules/pipelines/mutations.js","lines":{"begin":11,"end":39}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `types.RECEIVE_LASTEST_PIPELINE_SUCCESS` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"0883c6639137c6b51cad4b57c0dd1c38","location":{"path":"app/assets/javascripts/ide/stores/modules/pipelines/mutations.js","lines":{"begin":11,"end":39}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 32 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"0d98be7e83cb9dbc1ca8775da0aa7d19","location":{"path":"app/assets/javascripts/ide/lib/common/model.js","lines":{"begin":6,"end":43}},"other_locations":[],"remediation_points":768000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initIde` has 31 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"34f27cc8f1c849369b0095674a48c531","location":{"path":"app/assets/javascripts/ide/index.js","lines":{"begin":11,"end":44}},"other_locations":[],"remediation_points":744000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Function `init` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"0c6f3babcfd2a42ae88ffe4cc05028dc","location":{"path":"app/assets/javascripts/pager.js","lines":{"begin":10,"end":10}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `constructor` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f19d7f17b74ab07e9651e12680b14b9d","location":{"path":"app/assets/javascripts/profile/gl_crop.js","lines":{"begin":12,"end":40}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `onModalShow` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"c0421daee7918d81456c8580b8878457","location":{"path":"app/assets/javascripts/profile/gl_crop.js","lines":{"begin":69,"end":101}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Function `initMrNotes` has 74 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"4dd3a8bed3022600ffaa03ba5cc77fe6","location":{"path":"app/assets/javascripts/mr_notes/index.js","lines":{"begin":10,"end":92}},"other_locations":[],"remediation_points":1776000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"`` has 24 functions (exceeds 20 allowed). Consider refactoring.","fingerprint":"815b15d961f84ea742e6dce8eb3f8a4d","location":{"path":"app/assets/javascripts/badges/store/actions.js","lines":{"begin":14,"end":167}},"other_locations":[],"remediation_points":1600000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_each` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cbc52cfd6436c771188ac8f4e355cb9b","location":{"path":"app/validators/branch_filter_validator.rb","lines":{"begin":16,"end":30}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_each` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a8179f4c2dfe625c27616c834d37a930","location":{"path":"app/validators/js_regex_validator.rb","lines":{"begin":4,"end":16}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_each` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0d1ca6a4b7e3294fdc110a8e0ea47f92","location":{"path":"app/validators/cluster_name_validator.rb","lines":{"begin":7,"end":25}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_each` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d62539a99ae9fabba3cd2cc96713cf09","location":{"path":"app/validators/abstract_path_validator.rb","lines":{"begin":23,"end":35}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `ProjectTeam` has 27 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"ce2cc10520ba3b4b7205e61120d67315","location":{"path":"app/models/project_team.rb","lines":{"begin":3,"end":194}},"other_locations":[],"remediation_points":1900000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `build_kube_client!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e929847f530fa79a8a58054b9aa3fb8e","location":{"path":"app/models/clusters/platforms/kubernetes.rb","lines":{"begin":125,"end":140}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `note.rb` has 325 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"5f0768574ccb05a5f08cf2e994799fa2","location":{"path":"app/models/note.rb","lines":{"begin":6,"end":474}},"other_locations":[],"remediation_points":2280000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `grouped_diff_discussions` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3e55577fec2bb70f0e880e0e8d0f42ec","location":{"path":"app/models/note.rb","lines":{"begin":150,"end":168}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `in_reply_to?` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a17100ec3750af84df807aff92514aa5","location":{"path":"app/models/note.rb","lines":{"begin":367,"end":382}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Note` has 56 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"5d5c3b8714fe08a2c7df66bc4bfdc538","location":{"path":"app/models/note.rb","lines":{"begin":6,"end":474}},"other_locations":[],"remediation_points":4800000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `track_greatest` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"ee586dc1932745869e3f752d180242c9","location":{"path":"app/models/internal_id.rb","lines":{"begin":55,"end":55}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `rename_descendants` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"41d2108d52e96b7975591b300d3b2e60","location":{"path":"app/models/route.rb","lines":{"begin":23,"end":51}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"complex_logic","content":{"body":""},"description":"Consider simplifying this complex logical expression.","location":{"path":"app/models/issue.rb","lines":{"begin":306,"end":313}},"other_locations":[],"remediation_points":400000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"1cd63fdd17237f7c081abfa37690a7fe"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `as_json` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"430a48264a7379f4d248d672bb16fe44","location":{"path":"app/models/issue.rb","lines":{"begin":250,"end":273}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `readable_by?` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"05cd07a39aaac900028cc3b49079c531","location":{"path":"app/models/issue.rb","lines":{"begin":301,"end":315}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Issue` has 30 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"9619613b02f7bb8841b9e19c688f5251","location":{"path":"app/models/issue.rb","lines":{"begin":5,"end":326}},"other_locations":[],"remediation_points":2200000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `cached_attr_reader` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2a0b7bc4cebaa36fff63efc3ada11817","location":{"path":"app/models/concerns/redis_cacheable.rb","lines":{"begin":10,"end":20}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `move_dir` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a75dea312f0b07cc41974f687ba6fc64","location":{"path":"app/models/concerns/storage/legacy_namespace.rb","lines":{"begin":7,"end":41}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `expand_hierarchy_for_child` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f5222419d454a07f66c83180e4789d18","location":{"path":"app/models/concerns/group_descendant.rb","lines":{"begin":37,"end":70}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `expand_hierarchy_for_child` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"5cd85b2e5055a268df55df99d4d5a94e","location":{"path":"app/models/concerns/group_descendant.rb","lines":{"begin":37,"end":70}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `avatar_path` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b6177dbe195b50f7a5b10734fbc4a8d2","location":{"path":"app/models/concerns/avatarable.rb","lines":{"begin":45,"end":72}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `with_reactive_cache` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a8e8f8a2e9ccd006ae8f61ca9db92d0c","location":{"path":"app/models/concerns/reactive_caching.rb","lines":{"begin":69,"end":84}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `exclusively_update_reactive_cache!` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2811b2ef3d5196f0b89cec43f1f2b465","location":{"path":"app/models/concerns/reactive_caching.rb","lines":{"begin":91,"end":103}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_binary_column_exists!` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"23444612f22a67bddc69d4fb3d9ab40a","location":{"path":"app/models/concerns/sha_attribute.rb","lines":{"begin":18,"end":37}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `current` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"37517b27e64777d7506564ebffc2a9fa","location":{"path":"app/models/concerns/cacheable_attributes.rb","lines":{"begin":44,"end":57}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `chronic_duration_attr_writer` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8acb577138f44873e03ae5712c65f31d","location":{"path":"app/models/concerns/chronic_duration_attribute.rb","lines":{"begin":13,"end":28}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `truncated_diff_lines` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8bade7fdd3ab51990a34487352c56728","location":{"path":"app/models/concerns/discussion_on_diff.rb","lines":{"begin":41,"end":59}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `calculate_reactive_cache` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ff34ec3cc17426f2d12b615f23171aad","location":{"path":"app/models/concerns/prometheus_adapter.rb","lines":{"begin":36,"end":47}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `position_between` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0f9f931c8a1748472719ac1b03432d6c","location":{"path":"app/models/concerns/relative_positioning.rb","lines":{"begin":119,"end":139}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `status_sql` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"46850ccc347015917b7f7d5813dfbb20","location":{"path":"app/models/concerns/has_status.rb","lines":{"begin":19,"end":46}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `has_internal_id` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1753643cebf17fe9b88665c4895f0bc6","location":{"path":"app/models/concerns/atomic_internal_id.rb","lines":{"begin":30,"end":56}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_by_full_path` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9de369584b4bc5dcd50146b7967ef3b0","location":{"path":"app/models/concerns/routable.rb","lines":{"begin":35,"end":60}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `where_full_path_in` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1d7711a695fff8bdfaa49dc578b0132c","location":{"path":"app/models/concerns/routable.rb","lines":{"begin":69,"end":91}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `manual_inverse_association` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"53ad262470fd01998ef23cb50fac0495","location":{"path":"app/models/concerns/manual_inverse_association.rb","lines":{"begin":7,"end":17}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `raw_participants` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"014b735c6a47b089e8ef4112c427cdf6","location":{"path":"app/models/concerns/participable.rb","lines":{"begin":72,"end":104}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `each_batch` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5ca9b6fe345855869ed62b41036772f6","location":{"path":"app/models/concerns/each_batch.rb","lines":{"begin":42,"end":81}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `each_batch` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e56746932db5150f690993cd09ec2c5a","location":{"path":"app/models/concerns/each_batch.rb","lines":{"begin":42,"end":81}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `add_authentication_token_field` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9ffe8c5e6736ff7519d2b0ad210f55dd","location":{"path":"app/models/concerns/token_authenticatable.rb","lines":{"begin":31,"end":60}},"other_locations":[],"remediation_points":1550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `to_hook_data` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"651dd445a68aa882af831870aac09fe6","location":{"path":"app/models/concerns/issuable.rb","lines":{"begin":264,"end":290}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `notes_with_associations` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"edf734b8128847bcceb4300c584ab8e9","location":{"path":"app/models/concerns/issuable.rb","lines":{"begin":318,"end":334}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `max_member_access_for_resource_ids` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9a3525a1273d674a7a5dd1b4599a6b80","location":{"path":"app/models/concerns/bulk_member_access_load.rb","lines":{"begin":12,"end":40}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `enum_with_nil` has a Cognitive Complexity of 20 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d195530dc1d564b6a5a2361310657dd7","location":{"path":"app/models/concerns/enum_with_nil.rb","lines":{"begin":7,"end":33}},"other_locations":[],"remediation_points":1650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `labels_str` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bf445c84a1f8ebfebacc811382517d81","location":{"path":"app/models/label_note.rb","lines":{"begin":77,"end":90}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `notifiable?` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"76278bc24d3f0c41a172b68e2f632daa","location":{"path":"app/models/notification_recipient.rb","lines":{"begin":31,"end":46}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `has_access?` has a Cognitive Complexity of 22 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4264d898878128898c4e12b32b905f8a","location":{"path":"app/models/notification_recipient.rb","lines":{"begin":88,"end":101}},"other_locations":[],"remediation_points":1850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_notification_setting` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cf0f0817b180212c8e825cbbed40b752","location":{"path":"app/models/notification_recipient.rb","lines":{"begin":144,"end":154}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `closest_non_global_group_notification_settting` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9f65343a59de0315d68462b18f90defe","location":{"path":"app/models/notification_recipient.rb","lines":{"begin":157,"end":169}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this method.","location":{"path":"app/models/notification_recipient.rb","lines":{"begin":43,"end":43}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"d25d7135d797df677ee520a8a97ae9a6"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `find_commits_by_message` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"0c28ee46d93aa21d37eeb5b2a26d394f","location":{"path":"app/models/repository.rb","lines":{"begin":161,"end":161}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `repository.rb` has 758 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"849ce5e0e3a20d8ed78311435ce5fce9","location":{"path":"app/models/repository.rb","lines":{"begin":3,"end":1058}},"other_locations":[],"remediation_points":8515200,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `keep_around` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8ffb83b66ddf0a719303059ae40ce506","location":{"path":"app/models/repository.rb","lines":{"begin":249,"end":262}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `tree` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"38d273d7d1372c09c7a5c165e170a642","location":{"path":"app/models/repository.rb","lines":{"begin":647,"end":659}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `next_branch` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"53a66b748f45002bcdd585986f4f828f","location":{"path":"app/models/repository.rb","lines":{"begin":684,"end":697}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Repository` has 128 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"1ffa67f7557cad6dc009a3331141e757","location":{"path":"app/models/repository.rb","lines":{"begin":5,"end":1058}},"other_locations":[],"remediation_points":12000000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `enabled` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"38454ee90e05a227558719509fe5ec29","location":{"path":"app/models/remote_mirror.rb","lines":{"begin":104,"end":111}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `RemoteMirror` has 24 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"24334db25a09b73ccd711bb06c29a822","location":{"path":"app/models/remote_mirror.rb","lines":{"begin":3,"end":229}},"other_locations":[],"remediation_points":1600000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `initialize` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9c206e4584e2321188004de82f54d910","location":{"path":"app/models/commit_range.rb","lines":{"begin":62,"end":82}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `user.rb` has 1050 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"11e77959335527c9cb28992c10d37866","location":{"path":"app/models/user.rb","lines":{"begin":3,"end":1502}},"other_locations":[],"remediation_points":12720000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `increment_failed_attempts!` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"51accad62fa466380528844722430a98","location":{"path":"app/models/user.rb","lines":{"begin":1253,"end":1264}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ensure_user_rights_and_limits` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c7a575c65331c7b917638baa8941d41c","location":{"path":"app/models/user.rb","lines":{"begin":1405,"end":1414}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `signup_domain_valid?` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a9b5f7bc9d9c55cfe5d888bed2fb420a","location":{"path":"app/models/user.rb","lines":{"begin":1416,"end":1441}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `User` has 178 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"f685bcbb6f822884dec234767db1861a","location":{"path":"app/models/user.rb","lines":{"begin":5,"end":1502}},"other_locations":[],"remediation_points":17000000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `project.rb` has 1664 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"531155f760c7355b54c66683855d1397","location":{"path":"app/models/project.rb","lines":{"begin":3,"end":2277}},"other_locations":[],"remediation_points":21561600,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create_or_update_import_data` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"669b48913bc191fe053ef3cf863c7b51","location":{"path":"app/models/project.rb","lines":{"begin":691,"end":706}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ensure_import_state` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c647270223f46682704c8933072ea030","location":{"path":"app/models/project.rb","lines":{"begin":732,"end":745}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check_limit` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"238a83e94e0d19d7e5bf774e737f1856","location":{"path":"app/models/project.rb","lines":{"begin":887,"end":899}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_or_initialize_services` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b5c439340a2145fd43e3c155a46b8084","location":{"path":"app/models/project.rb","lines":{"begin":1084,"end":1110}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check_repository_path_availability` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"84ad9cf46f3288017a0f4b6db6438a6c","location":{"path":"app/models/project.rb","lines":{"begin":1302,"end":1316}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `after_create_default_branch` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2a2d29b10f840adb402e27183b8afb0e","location":{"path":"app/models/project.rb","lines":{"begin":1685,"end":1704}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `container_registry_variables` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"22330faec24429812d37099efaf8566b","location":{"path":"app/models/project.rb","lines":{"begin":1811,"end":1821}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `handle_update_attribute_error` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"103e8043443b236e57b2b71b4071ed2a","location":{"path":"app/models/project.rb","lines":{"begin":2240,"end":2250}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `fetch_branch_allows_collaboration?` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0a44b195efa5fdd8f8a133a9382fe540","location":{"path":"app/models/project.rb","lines":{"begin":2252,"end":2276}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Project` has 258 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"64039fbe3749f7d87b4ba7e85868d7fc","location":{"path":"app/models/project.rb","lines":{"begin":5,"end":2277}},"other_locations":[],"remediation_points":25000000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `service.rb` has 261 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"f45455ad20cdb5129a3bfb9eebb6498b","location":{"path":"app/models/service.rb","lines":{"begin":5,"end":346}},"other_locations":[],"remediation_points":1358400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Service` has 40 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"96ce9f4a9d59b553ec55116c8a3be7ae","location":{"path":"app/models/service.rb","lines":{"begin":5,"end":346}},"other_locations":[],"remediation_points":3200000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `available_services_names` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f2ec9d5c5ae446fb75d746688a3a72fe","location":{"path":"app/models/service.rb","lines":{"begin":247,"end":284}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `raw_binary?` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0ae3aeb7a760efb26099272499cfbade","location":{"path":"app/models/blob.rb","lines":{"begin":161,"end":175}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Blob` has 27 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"a44f4d9e927f1b91399b8e17fb943f6d","location":{"path":"app/models/blob.rb","lines":{"begin":4,"end":254}},"other_locations":[],"remediation_points":1900000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `event.rb` has 309 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"6850b538f65ad91cf5c5ef2e7148924e","location":{"path":"app/models/event.rb","lines":{"begin":3,"end":410}},"other_locations":[],"remediation_points":2049600,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `visible_to_user?` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.","fingerprint":"29269e08d03641e18bd2ba7cd22a0d3f","location":{"path":"app/models/event.rb","lines":{"begin":151,"end":167}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `action_name` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f28caae33540f5326e5ce2d58a03d771","location":{"path":"app/models/event.rb","lines":{"begin":265,"end":287}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Event` has 50 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"613c580b618dad28f34b389f766efe2e","location":{"path":"app/models/event.rb","lines":{"begin":3,"end":410}},"other_locations":[],"remediation_points":4200000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `different_group` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2ac2c358921313bb5981e964133c5ecf","location":{"path":"app/models/project_group_link.rb","lines":{"begin":38,"end":49}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `merge_request_version_params` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8d1a8357abf058d725c59df9cca495bf","location":{"path":"app/models/diff_discussion.rb","lines":{"begin":25,"end":36}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `active?` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"772f6211e69bcffb4fbc411c5d7d31c3","location":{"path":"app/models/legacy_diff_note.rb","lines":{"begin":57,"end":75}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this method.","location":{"path":"app/models/legacy_diff_note.rb","lines":{"begin":62,"end":62}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"4e0039e61f3fd67caeb1b978db868447"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `group.rb` has 290 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"d9e231fdbc15e514e21e1596b5e2d930","location":{"path":"app/models/group.rb","lines":{"begin":3,"end":403}},"other_locations":[],"remediation_points":1776000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Group` has 53 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"f82613684deaaf389431887bc5a62c74","location":{"path":"app/models/group.rb","lines":{"begin":5,"end":403}},"other_locations":[],"remediation_points":4500000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Label` has 25 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"9660fb59aa00ace0b608078c78c611a4","location":{"path":"app/models/label.rb","lines":{"begin":3,"end":244}},"other_locations":[],"remediation_points":1700000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `has_intermediates?` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fc033b957f3fcb1bd690b7edb50aa441","location":{"path":"app/models/pages_domain.rb","lines":{"begin":79,"end":98}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `PagesDomain` has 24 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"389a0c10cffbc7cd770d8d67a31e61ba","location":{"path":"app/models/pages_domain.rb","lines":{"begin":3,"end":201}},"other_locations":[],"remediation_points":1600000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `generate_slug` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0d507615693230a051c9d4ae397fe5d2","location":{"path":"app/models/environment.rb","lines":{"begin":178,"end":203}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Environment` has 27 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"4140683d4224f01a7fe2fad0da3d11c6","location":{"path":"app/models/environment.rb","lines":{"begin":3,"end":243}},"other_locations":[],"remediation_points":1900000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `ProjectWiki` has 29 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"a7ff4b093a326a93bf1e0887a36ba898","location":{"path":"app/models/project_wiki.rb","lines":{"begin":3,"end":203}},"other_locations":[],"remediation_points":2100000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `track` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d55ffd71690c0e606bcb1fa48074affd","location":{"path":"app/models/user_interacted_project.rb","lines":{"begin":16,"end":41}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8737f9bae4ea49696d911b5b162784ef","location":{"path":"app/models/wiki_page.rb","lines":{"begin":215,"end":244}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `process_title` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"43c91a5205fef35946442db85ab54267","location":{"path":"app/models/wiki_page.rb","lines":{"begin":283,"end":295}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `WikiPage` has 35 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"35e7d7ce82a5641563ba1029c54645bc","location":{"path":"app/models/wiki_page.rb","lines":{"begin":4,"end":326}},"other_locations":[],"remediation_points":2700000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update_position` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fa4f67c27ab14379e44af70d58264c1a","location":{"path":"app/models/diff_note.rb","lines":{"begin":152,"end":173}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `append` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d527adfd814d095e55079d7a898a3fc3","location":{"path":"app/models/ci/build_trace_chunk.rb","lines":{"begin":74,"end":84}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `pipeline.rb` has 488 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"cee8532683fbbb22a3b807c5714c49c7","location":{"path":"app/models/ci/pipeline.rb","lines":{"begin":3,"end":671}},"other_locations":[],"remediation_points":4627200,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ci_yaml_from_repo` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f19177ae4080cc2c9d81a39383b45922","location":{"path":"app/models/ci/pipeline.rb","lines":{"begin":632,"end":639}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Pipeline` has 68 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"af1e8c39d8b166628ae043bce72269a5","location":{"path":"app/models/ci/pipeline.rb","lines":{"begin":4,"end":670}},"other_locations":[],"remediation_points":6000000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `build.rb` has 604 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"7967fd1fb861d27e33c208d88261cd76","location":{"path":"app/models/ci/build.rb","lines":{"begin":3,"end":809}},"other_locations":[],"remediation_points":6297600,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `scoped_variables` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e310e4c57a2e9123f26a1cbcec9809eb","location":{"path":"app/models/ci/build.rb","lines":{"begin":312,"end":327}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `predefined_variables` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"13282d4d569e2daa02632e5249568733","location":{"path":"app/models/ci/build.rb","lines":{"begin":726,"end":745}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `legacy_variables` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"81e2fd25bb2dd91faed640216e965698","location":{"path":"app/models/ci/build.rb","lines":{"begin":747,"end":759}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `persisted_environment_variables` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"67327cbc303019830fbd1d6586dcde3e","location":{"path":"app/models/ci/build.rb","lines":{"begin":761,"end":772}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Build` has 100 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"736c24725c78f957ee4538a1ef73cab0","location":{"path":"app/models/ci/build.rb","lines":{"begin":4,"end":808}},"other_locations":[],"remediation_points":9200000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `status` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c700cdde3d0b6292dd18227b3e90cf8d","location":{"path":"app/models/ci/runner.rb","lines":{"begin":179,"end":187}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Runner` has 31 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"a9c3d8a2409fd9c5506a846374c16d42","location":{"path":"app/models/ci/runner.rb","lines":{"begin":4,"end":320}},"other_locations":[],"remediation_points":2300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Discussion` has 23 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"21189089f43b68ee94981dbac970d566","location":{"path":"app/models/discussion.rb","lines":{"begin":6,"end":138}},"other_locations":[],"remediation_points":1500000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `milestone_format_reference` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0e735e47b6b8a533b9ff579e13b277c9","location":{"path":"app/models/milestone.rb","lines":{"begin":261,"end":273}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Milestone` has 26 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"283d777de6e0e7974ccbb1e316f5fd1a","location":{"path":"app/models/milestone.rb","lines":{"begin":3,"end":288}},"other_locations":[],"remediation_points":1800000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0d0fba50527ad864dc4649245f95f1bf","location":{"path":"app/models/project_services/drone_ci_service.rb","lines":{"begin":22,"end":31}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `calculate_reactive_cache` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"aa74471515edc43c1ab5339ddf68e505","location":{"path":"app/models/project_services/drone_ci_service.rb","lines":{"begin":53,"end":74}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0a0446e31eb4d865b34fe5938f53bbad","location":{"path":"app/models/project_services/chat_notification_service.rb","lines":{"begin":52,"end":79}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `get_message` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"07a9999f0d84368d5983e08e2891f237","location":{"path":"app/models/project_services/chat_notification_service.rb","lines":{"begin":111,"end":126}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `notify_for_ref?` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6a030ca161112b5f434986ab0ae88c0f","location":{"path":"app/models/project_services/chat_notification_service.rb","lines":{"begin":159,"end":171}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `ChatNotificationService` has 23 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"5d159cd60a69a3f1a1b5beb530264825","location":{"path":"app/models/project_services/chat_notification_service.rb","lines":{"begin":5,"end":183}},"other_locations":[],"remediation_points":1500000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `read_commit_status` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"69dbb9ff98b20cc8db3a809b7e239e5f","location":{"path":"app/models/project_services/mock_ci_service.rb","lines":{"begin":70,"end":84}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `KubernetesService` has 25 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"2e6c7a0448e9bb4e799cb7efe0846fc7","location":{"path":"app/models/project_services/kubernetes_service.rb","lines":{"begin":8,"end":251}},"other_locations":[],"remediation_points":1700000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `read_commit_status` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8fb7749a6cc98bccd8d5712c1d499494","location":{"path":"app/models/project_services/teamcity_service.rb","lines":{"begin":112,"end":132}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `initialize_properties` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a965dd277aee3504a57790eb7271cadb","location":{"path":"app/models/project_services/issue_tracker_service.rb","lines":{"begin":52,"end":69}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `buildkite_endpoint` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"161d47385d79643f9414ed2ad67502a9","location":{"path":"app/models/project_services/buildkite_service.rb","lines":{"begin":106,"end":119}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"42914696303830e3d209925e13650d83","location":{"path":"app/models/project_services/pushover_service.rb","lines":{"begin":67,"end":104}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `fields` has 39 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"773d8cd6842c7b74f60e9ab8003ad977","location":{"path":"app/models/project_services/pushover_service.rb","lines":{"begin":21,"end":61}},"other_locations":[],"remediation_points":936000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `execute` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"7b760b5c22532a27dbd776328b7db9a6","location":{"path":"app/models/project_services/pushover_service.rb","lines":{"begin":67,"end":104}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"802d66c5bd557b6f1bdd32240846e4c2","location":{"path":"app/models/project_services/pipelines_email_service.rb","lines":{"begin":28,"end":38}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `jira_service.rb` has 261 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"c5f438f6b31722193eb4e10b389655b6","location":{"path":"app/models/project_services/jira_service.rb","lines":{"begin":3,"end":344}},"other_locations":[],"remediation_points":1358400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `close_issue` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f74475ea9bd04b9ed8ff7ddcc5c29633","location":{"path":"app/models/project_services/jira_service.rb","lines":{"begin":117,"end":135}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create_cross_reference_note` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6c27b1c9b77bd4f2d2f11a294e90cf36","location":{"path":"app/models/project_services/jira_service.rb","lines":{"begin":137,"end":167}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `JiraService` has 35 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"2d0643b626f1ad53bf43aadedd954eae","location":{"path":"app/models/project_services/jira_service.rb","lines":{"begin":3,"end":344}},"other_locations":[],"remediation_points":2700000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `read_commit_status` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f2f5469088e256c3b81eccc8ecaf967b","location":{"path":"app/models/project_services/bamboo_service.rb","lines":{"begin":94,"end":112}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create_message` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fcb9db20f4fe6adda5925e7da947a943","location":{"path":"app/models/project_services/hipchat_service.rb","lines":{"begin":85,"end":100}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create_push_message` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7f972562d25c4198e75c8dac6931a928","location":{"path":"app/models/project_services/hipchat_service.rb","lines":{"begin":106,"end":138}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `HipchatService` has 27 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"bb12852ed6419e4dea808d1d873302a1","location":{"path":"app/models/project_services/hipchat_service.rb","lines":{"begin":3,"end":311}},"other_locations":[],"remediation_points":1900000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `create_note_message` has 39 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"96500251c9498d9f1e9c7da0c8384528","location":{"path":"app/models/project_services/hipchat_service.rb","lines":{"begin":200,"end":244}},"other_locations":[],"remediation_points":936000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `format_channel` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e064d1923f5ef24f603e3c03af81644f","location":{"path":"app/models/project_services/irker_service.rb","lines":{"begin":93,"end":112}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check_commit` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d6ee148a09cf8c383948472d577bfb74","location":{"path":"app/models/project_services/asana_service.rb","lines":{"begin":81,"end":108}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Namespace` has 34 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"411d817f1a6db72f360bb2cb93e055ec","location":{"path":"app/models/namespace.rb","lines":{"begin":3,"end":302}},"other_locations":[],"remediation_points":2600000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `application_setting.rb` has 387 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"50270695e6d7c5163050078e7cf65e18","location":{"path":"app/models/application_setting.rb","lines":{"begin":3,"end":489}},"other_locations":[],"remediation_points":3172800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `ApplicationSetting` has 38 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"02be76880912851331582ab14ef56dd4","location":{"path":"app/models/application_setting.rb","lines":{"begin":3,"end":489}},"other_locations":[],"remediation_points":3000000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `defaults` has 76 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"4a5e7733302a5f59e7878ab40f6b9f82","location":{"path":"app/models/application_setting.rb","lines":{"begin":232,"end":309}},"other_locations":[],"remediation_points":1824000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `base_commit_sha` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"70d6880ba8e52f184e9eaf896119e352","location":{"path":"app/models/compare.rb","lines":{"begin":50,"end":56}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `commit.rb` has 355 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"c361e4106ded93f757f69964f420b067","location":{"path":"app/models/commit.rb","lines":{"begin":4,"end":508}},"other_locations":[],"remediation_points":2712000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `order_by` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8c4e54c836dbfddf9329a85f83378cb5","location":{"path":"app/models/commit.rb","lines":{"begin":63,"end":75}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `uri_type` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ac39422eaa05c0e3880afe76c6341f18","location":{"path":"app/models/commit.rb","lines":{"begin":425,"end":435}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Commit` has 68 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"e2175bec55ffd09f5d7b2352b5c5dcad","location":{"path":"app/models/commit.rb","lines":{"begin":4,"end":508}},"other_locations":[],"remediation_points":6000000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `merge_request.rb` has 897 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"59d143493fc6a1f49dd6ff46bba4459f","location":{"path":"app/models/merge_request.rb","lines":{"begin":3,"end":1238}},"other_locations":[],"remediation_points":10516800,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_branches` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"95d0d3fa4206c77bab101daf5035ee18","location":{"path":"app/models/merge_request.rb","lines":{"begin":511,"end":524}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_fork` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"68cf8c7f9bbbebf2133c22434d2bcae9","location":{"path":"app/models/merge_request.rb","lines":{"begin":532,"end":539}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `mergeable_state?` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bdc249b8456dc07607ba4fe001ff1261","location":{"path":"app/models/merge_request.rb","lines":{"begin":677,"end":685}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `cache_merge_request_closes_issues!` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bcd9dc4beba740e5c019b9c760cacdd3","location":{"path":"app/models/merge_request.rb","lines":{"begin":769,"end":782}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `compare_test_reports` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b662bb8a5b01a77feb8b7b0a9ffefdb2","location":{"path":"app/models/merge_request.rb","lines":{"begin":1041,"end":1054}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `mergeable_with_quick_action?` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"74fd9deeffa5589190ba744e4af78384","location":{"path":"app/models/merge_request.rb","lines":{"begin":1177,"end":1187}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `MergeRequest` has 137 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"417a10e5eb9495ae2d250b8c195971f7","location":{"path":"app/models/merge_request.rb","lines":{"begin":3,"end":1238}},"other_locations":[],"remediation_points":12900000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this method.","location":{"path":"app/models/merge_request.rb","lines":{"begin":682,"end":682}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"ac1600795e5aff84e3d4e9243b88bdbc"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this method.","location":{"path":"app/models/merge_request.rb","lines":{"begin":1184,"end":1184}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"4cc86fde220f02f2e1c1443c9c96fe93"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `member.rb` has 297 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"ead9a1c0eaecbe741f755dd85ef04b38","location":{"path":"app/models/member.rb","lines":{"begin":3,"end":419}},"other_locations":[],"remediation_points":1876800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `retrieve_member` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9cf98664280114d55fe07fc00a2931f4","location":{"path":"app/models/member.rb","lines":{"begin":236,"end":248}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Member` has 41 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"5c62b78b2bb4b49f04e64fa24a5e7525","location":{"path":"app/models/member.rb","lines":{"begin":3,"end":419}},"other_locations":[],"remediation_points":3300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `set` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8869fa571b5fb7a617adc0197c7ac362","location":{"path":"app/models/active_session.rb","lines":{"begin":20,"end":50}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `count_to_display_commit_in_center` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b411aa7c155fd11004316e7fd39fd0f0","location":{"path":"app/models/network/graph.rb","lines":{"begin":79,"end":107}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `place_chain` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"177c4e2e8eb1351747941148b8c5841f","location":{"path":"app/models/network/graph.rb","lines":{"begin":180,"end":216}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `take_left_leaves` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"07eb1c8a024e1084d5249e442c56ead6","location":{"path":"app/models/network/graph.rb","lines":{"begin":263,"end":277}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `place_chain` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d89c4cb61eca0dac9322942ca4b87aff","location":{"path":"app/models/network/graph.rb","lines":{"begin":180,"end":216}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `save_diffs` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c6b7ee6cba58c9eb3d9ad5eed75d6ec3","location":{"path":"app/models/merge_request_diff.rb","lines":{"begin":272,"end":296}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `MergeRequestDiff` has 33 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"d7a32bf8ffc7cc2cb0b417114258fc44","location":{"path":"app/models/merge_request_diff.rb","lines":{"begin":3,"end":322}},"other_locations":[],"remediation_points":2500000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `object_storage.rb` has 315 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"86494fa0d557a4aa389e5171896783dc","location":{"path":"app/uploaders/object_storage.rb","lines":{"begin":3,"end":458}},"other_locations":[],"remediation_points":2136000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `changed_mounts` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fd0a4f3d9448639f581ace634e78fb9f","location":{"path":"app/uploaders/object_storage.rb","lines":{"begin":113,"end":124}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `workhorse_remote_upload_options` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e022c746daf5a42b94b0696cad68db33","location":{"path":"app/uploaders/object_storage.rb","lines":{"begin":191,"end":201}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `unsafe_migrate!` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1684da513e48bc7fed9d7d05f006f4f3","location":{"path":"app/uploaders/object_storage.rb","lines":{"begin":415,"end":442}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `record_upload` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7714f01e58a6f57d5612758293fdcd98","location":{"path":"app/uploaders/records_uploads.rb","lines":{"begin":22,"end":32}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `open` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e3420c6ebb4c5709171dfe0ad626a693","location":{"path":"app/uploaders/gitlab_uploader.rb","lines":{"begin":78,"end":94}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `FileUploader` has 25 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"377d9736f93da216ab2620478b97b6a7","location":{"path":"app/uploaders/file_uploader.rb","lines":{"begin":11,"end":200}},"other_locations":[],"remediation_points":1700000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ci_status` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0ecf5e5baa621e39be46cda7a6b7e79a","location":{"path":"app/presenters/merge_request_presenter.rb","lines":{"begin":13,"end":23}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `MergeRequestPresenter` has 29 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"8382c7b1054d55cea9b796fc549636ad","location":{"path":"app/presenters/merge_request_presenter.rb","lines":{"begin":3,"end":231}},"other_locations":[],"remediation_points":2100000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `cards` has 79 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"56c716082acebaebac8f5817c4770ff3","location":{"path":"app/presenters/conversational_development_index/metric_presenter.rb","lines":{"begin":5,"end":85}},"other_locations":[],"remediation_points":1896000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `idea_to_production_steps` has 52 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"3ec38351e7aae7c1eb1bbf782da34f4f","location":{"path":"app/presenters/conversational_development_index/metric_presenter.rb","lines":{"begin":87,"end":140}},"other_locations":[],"remediation_points":1248000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `project_presenter.rb` has 312 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"d892daae824d8e78fe2a0b68c288acda","location":{"path":"app/presenters/project_presenter.rb","lines":{"begin":3,"end":370}},"other_locations":[],"remediation_points":2092800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `default_view` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d66dbcb27efcacaf84388d8cfc5f690f","location":{"path":"app/presenters/project_presenter.rb","lines":{"begin":64,"end":80}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `readme_anchor_data` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c40df23b551a8abe9f2d5830edfbf472","location":{"path":"app/presenters/project_presenter.rb","lines":{"begin":221,"end":231}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `autodevops_anchor_data` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3e4902e11edd6de01b81f9a61f5d9d7b","location":{"path":"app/presenters/project_presenter.rb","lines":{"begin":275,"end":285}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `kubernetes_cluster_anchor_data` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"119e536f7905363201fb67bbdcd65824","location":{"path":"app/presenters/project_presenter.rb","lines":{"begin":287,"end":299}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `ProjectPresenter` has 40 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"174173017516e33835d52eb7c5513209","location":{"path":"app/presenters/project_presenter.rb","lines":{"begin":3,"end":370}},"other_locations":[],"remediation_points":3200000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `notification_service.rb` has 356 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"69e4d6c40c4449fefd31aa10ca6d0d74","location":{"path":"app/services/notification_service.rb","lines":{"begin":18,"end":550}},"other_locations":[],"remediation_points":2726400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `NotificationService` has 57 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"f69a1b203ea4d5900672119cebb81b66","location":{"path":"app/services/notification_service.rb","lines":{"begin":18,"end":550}},"other_locations":[],"remediation_points":4900000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"40698d762fba8b2aa8a60c141d6d8790","location":{"path":"app/services/groups/create_service.rb","lines":{"begin":10,"end":29}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `can_create_group?` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b75b73bf2d690e5a17c2abe284491440","location":{"path":"app/services/groups/create_service.rb","lines":{"begin":37,"end":54}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ensure_allowed_transfer` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b1a41885cc0a6ac544248d54c07e1636","location":{"path":"app/services/groups/transfer_service.rb","lines":{"begin":41,"end":47}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4bb4279e0d059ae00152e8bdf1ea798d","location":{"path":"app/services/groups/update_service.rb","lines":{"begin":7,"end":25}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `get_operation_id` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e02445fcf7469e25e1790f6de102578e","location":{"path":"app/services/clusters/gcp/provision_service.rb","lines":{"begin":24,"end":48}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ee86b571c5fd71a16cf372ae35959a1d","location":{"path":"app/services/clusters/applications/check_installation_progress_service.rb","lines":{"begin":6,"end":19}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c7bf557578299a5ba915c32a04c77757","location":{"path":"app/services/gravatar_service.rb","lines":{"begin":4,"end":18}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `valid_visibility_level_change?` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5663649284b422de3ee165079f02580a","location":{"path":"app/services/concerns/update_visibility_level.rb","lines":{"begin":4,"end":16}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1e2214a2c7403de3b6b130ef6a8ae0e7","location":{"path":"app/services/labels/transfer_service.rb","lines":{"begin":15,"end":29}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"889d22762e16d3366a67e51333a2ca58","location":{"path":"app/services/labels/promote_service.rb","lines":{"begin":8,"end":30}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"83f2800751c8e1fc38441feba44838e9","location":{"path":"app/services/notes/create_service.rb","lines":{"begin":5,"end":53}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `execute` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"64400ce10f6b24c336dc2c14dc7631e5","location":{"path":"app/services/notes/create_service.rb","lines":{"begin":5,"end":53}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `project` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"55c1155f35f19aeab86111aa747bcdef","location":{"path":"app/services/search_service.rb","lines":{"begin":12,"end":22}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `group` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9350f7f8937ed59a3c058c18876a72fc","location":{"path":"app/services/search_service.rb","lines":{"begin":26,"end":36}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `add_commits` has 6 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"eb5157bfd09a812f4b9e9e59faa9bd43","location":{"path":"app/services/system_note_service.rb","lines":{"begin":22,"end":22}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `change_status` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"1b2b9c2adcef9eef8232ca3fabba5213","location":{"path":"app/services/system_note_service.rb","lines":{"begin":214,"end":214}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `change_branch` has 6 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"d01b8c3144cf8ecd33ca899268167ba5","location":{"path":"app/services/system_note_service.rb","lines":{"begin":370,"end":370}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `change_branch_presence` has 6 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"dcdc7164fbd2b97a6ffca4f6dc2e0e44","location":{"path":"app/services/system_note_service.rb","lines":{"begin":390,"end":390}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `system_note_service.rb` has 269 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"9d114264549f2dceede46ea914a3550c","location":{"path":"app/services/system_note_service.rb","lines":{"begin":7,"end":679}},"other_locations":[],"remediation_points":1473600,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `change_time_spent` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5c81cb9f69e1de7b23d6f18ba82533d3","location":{"path":"app/services/system_note_service.rb","lines":{"begin":181,"end":197}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `cross_reference_disallowed?` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cf012be1a6a2e48dd333008fe9d0d152","location":{"path":"app/services/system_note_service.rb","lines":{"begin":455,"end":461}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `existing_commit_summary` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9e510f603309baf5e0cb54ad77970a75","location":{"path":"app/services/system_note_service.rb","lines":{"begin":637,"end":659}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `create_mention_todos` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"8a610af9d7608ea64f58ba654367d763","location":{"path":"app/services/todo_service.rb","lines":{"begin":273,"end":273}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `attributes_for_todo` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"22c5b886bc2bcef31fd1446dabf42446","location":{"path":"app/services/todo_service.rb","lines":{"begin":310,"end":310}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `TodoService` has 43 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"888b933ca386de89d1c664deed0728bd","location":{"path":"app/services/todo_service.rb","lines":{"begin":10,"end":357}},"other_locations":[],"remediation_points":3500000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bcfd0e3ea01c9694d57bfccaa03838e6","location":{"path":"app/services/verify_pages_domain_service.rb","lines":{"begin":18,"end":28}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d473aafcb22747b52b1f62e820f2074d","location":{"path":"app/services/create_branch_service.rb","lines":{"begin":4,"end":21}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9a39c0c805730f76077d4c41f5a49ab0","location":{"path":"app/services/todos/destroy/entity_leave_service.rb","lines":{"begin":21,"end":35}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"dd806bba9fb1ffe1bffc98d5b313f431","location":{"path":"app/services/todos/destroy/private_features_service.rb","lines":{"begin":14,"end":25}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"669e705b9b727535b65aa70c23908b8a","location":{"path":"app/services/issuable/bulk_update_service.rb","lines":{"begin":6,"end":31}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fa059d9d5e3d40934b6d454456a29e17","location":{"path":"app/services/issuable/common_system_notes_service.rb","lines":{"begin":7,"end":21}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `process_registry_access` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0a9f93af474354f18c131c4a6f33ce74","location":{"path":"app/services/auth/container_registry_authentication_service.rb","lines":{"begin":71,"end":77}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `process_repository_access` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d2612abf7127bdfa9f619c175cf5d2cc","location":{"path":"app/services/auth/container_registry_authentication_service.rb","lines":{"begin":79,"end":97}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7189b60fb2402fbb02818934986a95e8","location":{"path":"app/services/projects/overwrite_project_service.rb","lines":{"begin":5,"end":26}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 26 (exceeds 5 allowed). Consider refactoring.","fingerprint":"961ae326b78bf20ee97acef0cec52327","location":{"path":"app/services/projects/create_service.rb","lines":{"begin":11,"end":74}},"other_locations":[],"remediation_points":2250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `after_create_actions` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e38e4c0872ce60d2ba85c11448b4522b","location":{"path":"app/services/projects/create_service.rb","lines":{"begin":98,"end":114}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `save_project_and_import_data` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"49a616d751623502896fe1bf01ddc263","location":{"path":"app/services/projects/create_service.rb","lines":{"begin":143,"end":158}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `execute` has 41 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"175fda5eebf084d959ea97dbc7ddba89","location":{"path":"app/services/projects/create_service.rb","lines":{"begin":11,"end":74}},"other_locations":[],"remediation_points":984000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this method.","location":{"path":"app/services/projects/create_service.rb","lines":{"begin":53,"end":53}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"4406c9c51c68a677aea6fc1c46031379"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `propagate_projects_with_template` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"293ec445efe1987a7ed4d749d241b1c9","location":{"path":"app/services/projects/propagate_service_template.rb","lines":{"begin":25,"end":33}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2a2218c0712e897508cb29f687ddbb15","location":{"path":"app/services/projects/destroy_service.rb","lines":{"begin":17,"end":49}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `attempt_repositories_rollback` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"06f3aaf8a1f066c4e7933483e129d710","location":{"path":"app/services/projects/destroy_service.rb","lines":{"begin":51,"end":63}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `add_repository_to_project` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0129f188de75c26a852812d70994f671","location":{"path":"app/services/projects/import_service.rb","lines":{"begin":33,"end":52}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_repository` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a7dc266875c644d2161df4f52854509a","location":{"path":"app/services/projects/import_service.rb","lines":{"begin":60,"end":78}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `download_lfs_objects` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e22b343575e9881c97f2271bcd97f948","location":{"path":"app/services/projects/import_service.rb","lines":{"begin":80,"end":99}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_data` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7bc52f032ae36171f9038ca564b8725f","location":{"path":"app/services/projects/import_service.rb","lines":{"begin":101,"end":109}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d3753f60f8dc5d9fdb996971cbdec0ee","location":{"path":"app/services/projects/update_pages_service.rb","lines":{"begin":18,"end":47}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `extract_zip_archive!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fe7709a469edc64e95ec098c80aa4d9e","location":{"path":"app/services/projects/update_pages_service.rb","lines":{"begin":84,"end":104}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `UpdatePagesService` has 23 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"d7d865dc59fc4a03d279c1c19c89a168","location":{"path":"app/services/projects/update_pages_service.rb","lines":{"begin":4,"end":191}},"other_locations":[],"remediation_points":1500000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `labels_as_hash` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"78f7382b2da989274c44cca4dd67b726","location":{"path":"app/services/projects/autocomplete_service.rb","lines":{"begin":25,"end":47}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ecc06d47c5ab14de0497085a390276e9","location":{"path":"app/services/projects/update_remote_mirror_service.rb","lines":{"begin":7,"end":31}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f166c36283c89cda2c2d1b400fc83d9c","location":{"path":"app/services/projects/transfer_service.rb","lines":{"begin":16,"end":36}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8619477bd476b8e6c20c420edaafa603","location":{"path":"app/services/projects/update_service.rb","lines":{"begin":10,"end":29}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate!` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"eab5a0a1e19f69711efa955efe37831a","location":{"path":"app/services/projects/update_service.rb","lines":{"begin":40,"end":52}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `after_update` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a1014f2a5df19a7195983026335ef76c","location":{"path":"app/services/projects/update_service.rb","lines":{"begin":54,"end":76}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"23b22fd0746b04d1cdb84c038d084b5b","location":{"path":"app/services/projects/hashed_storage/migrate_repository_service.rb","lines":{"begin":18,"end":46}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0543ffb09bae340f0c5077ae46b7cdcc","location":{"path":"app/services/projects/lfs_pointers/lfs_download_service.rb","lines":{"begin":8,"end":23}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"896cabbb6ed77bf3c709bb312f23a6fb","location":{"path":"app/services/projects/lfs_pointers/lfs_import_service.rb","lines":{"begin":17,"end":32}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `lfsconfig_endpoint_uri` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ab48443c563d169a420e80faa5c91bf8","location":{"path":"app/services/projects/lfs_pointers/lfs_import_service.rb","lines":{"begin":56,"end":72}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0d7377f3f82a92babb9d1d653fb3a6c0","location":{"path":"app/services/projects/unlink_fork_service.rb","lines":{"begin":6,"end":29}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `notification_recipient_service.rb` has 261 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"04116f97d81f75dc8b45ce0a5811fa81","location":{"path":"app/services/notification_recipient_service.rb","lines":{"begin":6,"end":384}},"other_locations":[],"remediation_points":1358400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `build!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bc4e88abd84c5b4832ac272b050f5372","location":{"path":"app/services/notification_recipient_service.rb","lines":{"begin":257,"end":295}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Base` has 28 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"27695a9b55119263d5fc023bfb91d6e3","location":{"path":"app/services/notification_recipient_service.rb","lines":{"begin":28,"end":238}},"other_locations":[],"remediation_points":2000000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6cc1d79d478940cb6d6b9ceed02cb6e6","location":{"path":"app/services/users/destroy_service.rb","lines":{"begin":26,"end":64}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute_without_lease` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"67cb8ef6b0457ad00d8347bcbe3ff3f7","location":{"path":"app/services/users/refresh_authorized_projects_service.rb","lines":{"begin":50,"end":71}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update_authorizations` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6e70e1ba1ea2f084af1fff74047fe8d9","location":{"path":"app/services/users/refresh_authorized_projects_service.rb","lines":{"begin":77,"end":88}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d9165689bde9f912aa84f6f518e598da","location":{"path":"app/services/users/build_service.rb","lines":{"begin":14,"end":36}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `build_user_params` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"830b5807353157bd5e183aad25f2b282","location":{"path":"app/services/users/build_service.rb","lines":{"begin":88,"end":111}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `admin_create_params` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"fe5ddceb25aafe04827303d1f6d564b4","location":{"path":"app/services/users/build_service.rb","lines":{"begin":45,"end":74}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5bc79f55d7e462657e85385bf64c461b","location":{"path":"app/services/submit_usage_ping_service.rb","lines":{"begin":16,"end":34}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"eff6af7a39548e896b0c36625e41edfa","location":{"path":"app/services/validate_new_branch_service.rb","lines":{"begin":6,"end":20}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f26b8395f9bebcbbe601568ab87f1dac","location":{"path":"app/services/tags/create_service.rb","lines":{"begin":5,"end":32}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"009acc0cc8f2cad1eb48f996869cd8b3","location":{"path":"app/services/tags/destroy_service.rb","lines":{"begin":6,"end":29}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `close_issue` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"268f126190d57eeaaded69cfb25897c3","location":{"path":"app/services/issues/close_service.rb","lines":{"begin":20,"end":39}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `handle_changes` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ff12a5753d6bee667c95cfbc92416e03","location":{"path":"app/services/issues/update_service.rb","lines":{"begin":18,"end":56}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `handle_changes` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8b14e45d1c64f39d6eedb20d84843741","location":{"path":"app/services/issues/update_service.rb","lines":{"begin":18,"end":56}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `filter_assignee` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5691687cb00f6fd194e603429807185a","location":{"path":"app/services/issues/base_service.rb","lines":{"begin":35,"end":51}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `execute` has 32 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"1a9702d3dc815e8d64756d8fb212f9b3","location":{"path":"app/services/web_hook_service.rb","lines":{"begin":24,"end":62}},"other_locations":[],"remediation_points":768000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `execute` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"4d9ca5643eb0d67f71faf5371f5ae764","location":{"path":"app/services/ci/create_pipeline_service.rb","lines":{"begin":15,"end":48}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b8c02fe4ca7afa344ed38920cbc647e3","location":{"path":"app/services/ci/retry_pipeline_service.rb","lines":{"begin":7,"end":28}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bc2e58626fb38ae2a635ad7504d1369f","location":{"path":"app/services/ci/stop_environments_service.rb","lines":{"begin":7,"end":18}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"617800a52fa424a1bcea1ed6a62b6793","location":{"path":"app/services/ci/register_job_service.rb","lines":{"begin":19,"end":66}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `execute` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"3c427dfa0dd24ca8d0170b6247260b87","location":{"path":"app/services/ci/register_job_service.rb","lines":{"begin":19,"end":66}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `process_label_ids` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d322faf3023b3e313c8abe2020cf59af","location":{"path":"app/services/issuable_base_service.rb","lines":{"begin":96,"end":111}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"139f887d96d46ca70a22e378422f8bc7","location":{"path":"app/services/issuable_base_service.rb","lines":{"begin":172,"end":224}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `IssuableBaseService` has 30 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"906133099e33d0679f4b8c65cc1eef9b","location":{"path":"app/services/issuable_base_service.rb","lines":{"begin":3,"end":319}},"other_locations":[],"remediation_points":2200000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `update` has 37 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"009042d6d3df2763254c61f2b3d8536c","location":{"path":"app/services/issuable_base_service.rb","lines":{"begin":172,"end":224}},"other_locations":[],"remediation_points":888000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `build_event_data` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"181f03475858936500cb1903a2981922","location":{"path":"app/services/system_hooks_service.rb","lines":{"begin":22,"end":70}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `build_event_name` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"58fb30a590bb73d7a7c014c15cff2f61","location":{"path":"app/services/system_hooks_service.rb","lines":{"begin":72,"end":83}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `build_event_data` has 41 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"cb1f235466d69ba0f51ae5082e6ccd1b","location":{"path":"app/services/system_hooks_service.rb","lines":{"begin":22,"end":70}},"other_locations":[],"remediation_points":984000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `include_any_scope?` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c3b269346955ff77f7af249761778c5f","location":{"path":"app/services/access_token_validation_service.rb","lines":{"begin":33,"end":48}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b944cb228f06d17cfab396e042f46404","location":{"path":"app/services/create_deployment_service.rb","lines":{"begin":15,"end":29}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2a911c4e00c978f2c3dee8a80d2b123c","location":{"path":"app/services/members/destroy_service.rb","lines":{"begin":5,"end":23}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3e17107f5166483e06d9a3aa43ce4057","location":{"path":"app/services/delete_branch_service.rb","lines":{"begin":4,"end":23}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `can_be_resolved_in_ui?` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"303b72ed1619302f77225ae41471638d","location":{"path":"app/services/merge_requests/conflicts/list_service.rb","lines":{"begin":15,"end":23}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `reload_merge_requests` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a105139484033c8e7ced10c6ce36e8db","location":{"path":"app/services/merge_requests/refresh_service.rb","lines":{"begin":80,"end":106}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_new_commits` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"851ed172a4510d8303c8caf10c7c09e1","location":{"path":"app/services/merge_requests/refresh_service.rb","lines":{"begin":119,"end":141}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `trigger` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8627834675a31ad536bf412edaafd7e7","location":{"path":"app/services/merge_requests/merge_when_pipeline_succeeds_service.rb","lines":{"begin":24,"end":33}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4cd7f00168c6c916f0dc7075f0ca3349","location":{"path":"app/services/merge_requests/merge_service.rb","lines":{"begin":17,"end":37}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"35e1a81d921bd211a80412b3021d8f1f","location":{"path":"app/services/merge_requests/create_from_issue_service.rb","lines":{"begin":16,"end":31}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_branches` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"629d86725d56fa929caf501972ac7cf2","location":{"path":"app/services/merge_requests/build_service.rb","lines":{"begin":92,"end":97}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `BuildService` has 22 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"0955c9bbce7bc1720631a8dc579bbf5f","location":{"path":"app/services/merge_requests/build_service.rb","lines":{"begin":4,"end":208}},"other_locations":[],"remediation_points":1400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `get_branches` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7e605b474bb1fb7b66f5bfbaf51d3c6d","location":{"path":"app/services/merge_requests/get_urls_service.rb","lines":{"begin":33,"end":50}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `handle_changes` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cefad5ecb48fbecc8f4fa6ba026659f8","location":{"path":"app/services/merge_requests/update_service.rb","lines":{"begin":27,"end":78}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `handle_changes` has 42 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"db391ef0b97408393fd716be0c7356dd","location":{"path":"app/services/merge_requests/update_service.rb","lines":{"begin":27,"end":78}},"other_locations":[],"remediation_points":1008000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `commit_status_merge_requests` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"784a58094574fa35608fca082c2548e7","location":{"path":"app/services/merge_requests/base_service.rb","lines":{"begin":76,"end":85}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `interpret_service.rb` has 596 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"83735b0b7c1f8d22c40f49e709c57e70","location":{"path":"app/services/quick_actions/interpret_service.rb","lines":{"begin":3,"end":700}},"other_locations":[],"remediation_points":6182400,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b08fd2ee935cfed15e0505bb9af417b4","location":{"path":"app/services/discussions/update_diff_position_service.rb","lines":{"begin":5,"end":34}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"dcb90a6db168562206b2356716e752b6","location":{"path":"app/services/git_push_service.rb","lines":{"begin":24,"end":65}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `index` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8b2781e6841d5d63745094c685fcd1e8","location":{"path":"app/controllers/groups/children_controller.rb","lines":{"begin":6,"end":29}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `index` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3169a6c4ee72f03a1318dedbed6b40ff","location":{"path":"app/controllers/groups/group_members_controller.rb","lines":{"begin":17,"end":39}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `failure_message` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e3605869f669f4cb85ba66bdd5b04c7a","location":{"path":"app/controllers/omniauth_callbacks_controller.rb","lines":{"begin":30,"end":38}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `omniauth_flow` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8cf6a8a0fd40eb5ff273cc6dba558403","location":{"path":"app/controllers/omniauth_callbacks_controller.rb","lines":{"begin":77,"end":95}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `sign_in_user_flow` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e780968a46460e3b3fe11a34ddfc0c00","location":{"path":"app/controllers/omniauth_callbacks_controller.rb","lines":{"begin":115,"end":136}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `OmniauthCallbacksController` has 22 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"af6b190bf330da08acbe854817a6812f","location":{"path":"app/controllers/omniauth_callbacks_controller.rb","lines":{"begin":3,"end":192}},"other_locations":[],"remediation_points":1400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `impersonate` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f01a1ec9614ccf1b226a3e42206b203b","location":{"path":"app/controllers/admin/users_controller.rb","lines":{"begin":33,"end":56}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"af47e69f6ee053a6f226de336934f214","location":{"path":"app/controllers/admin/users_controller.rb","lines":{"begin":118,"end":147}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `UsersController` has 23 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"bb3ecebd549737d88ffb4ea7b2d02d16","location":{"path":"app/controllers/admin/users_controller.rb","lines":{"begin":3,"end":230}},"other_locations":[],"remediation_points":1500000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `show` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4d5e93c6a88076ccc249e69590f05f53","location":{"path":"app/controllers/admin/system_info_controller.rb","lines":{"begin":34,"end":57}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `show` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6c80ea5d1d0a024abdc38fba56cb8435","location":{"path":"app/controllers/concerns/uploads_actions.rb","lines":{"begin":35,"end":48}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `send_blob` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cfa9da0619389a51f42df3b51bf5c65c","location":{"path":"app/controllers/concerns/sends_blob.rb","lines":{"begin":11,"end":25}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `leave` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1f6860cf601d992df6f0af95d95748a0","location":{"path":"app/controllers/concerns/membership_actions.rb","lines":{"begin":63,"end":82}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `set_issuables_index` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f415a6c750063cabd8f90d48e98e5eb0","location":{"path":"app/controllers/concerns/issuable_collections.rb","lines":{"begin":17,"end":38}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `authenticate_with_two_factor` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"36f3a297fab9a3ef0e526b7f851741b2","location":{"path":"app/controllers/concerns/authenticates_with_two_factor.rb","lines":{"begin":43,"end":54}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `safe_redirect_path` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"36420be74fc50045ef291eb40a71bb77","location":{"path":"app/controllers/concerns/internal_redirect.rb","lines":{"begin":6,"end":17}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `safe_redirect_path_for_url` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d5b751dbd9dd362815b574f1cb52225a","location":{"path":"app/controllers/concerns/internal_redirect.rb","lines":{"begin":19,"end":26}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `note_json` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"273ea02f6c72cced154034c12207d3f1","location":{"path":"app/controllers/concerns/notes_actions.rb","lines":{"begin":104,"end":146}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `diff_discussion_html` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"62bc37c3eb9f037889edcbe39ef53aa6","location":{"path":"app/controllers/concerns/notes_actions.rb","lines":{"begin":148,"end":174}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `note_project` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"573f594e00ed6b29987ce9e763457833","location":{"path":"app/controllers/concerns/notes_actions.rb","lines":{"begin":239,"end":256}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `note_json` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"2572ca1fa7eb27c9668322b7a098f02a","location":{"path":"app/controllers/concerns/notes_actions.rb","lines":{"begin":104,"end":146}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `recaptcha_check_with_fallback` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f5ab9c6deb983a187870f912e14afaea","location":{"path":"app/controllers/concerns/spammable_actions.rb","lines":{"begin":29,"end":54}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create_commit` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"832aa36f617c6cce4927e6c8b561ce98","location":{"path":"app/controllers/concerns/creates_commit.rb","lines":{"begin":8,"end":51}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update_flash_notice` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"89030f5b74776bb2425f95279fdd5b7d","location":{"path":"app/controllers/concerns/creates_commit.rb","lines":{"begin":62,"end":73}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `final_success_path` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"afb1305c0c32730e0b027be235b174a0","location":{"path":"app/controllers/concerns/creates_commit.rb","lines":{"begin":75,"end":83}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `create_commit` has 35 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"eaa2bdf0bf7bb61da1f37f4031392e35","location":{"path":"app/controllers/concerns/creates_commit.rb","lines":{"begin":8,"end":51}},"other_locations":[],"remediation_points":840000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `send_upload` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cfa021a786ee32f97584fecedce60cde","location":{"path":"app/controllers/concerns/send_file_upload.rb","lines":{"begin":4,"end":26}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `lfs_check_access!` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"46d5167d71a5e6a69a9a236086347aed","location":{"path":"app/controllers/concerns/lfs_request.rb","lines":{"begin":36,"end":45}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"905d81937e68b3931dad7416dc6ef9a6","location":{"path":"app/controllers/import/bitbucket_controller.rb","lines":{"begin":37,"end":65}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"20b781fcddc55e961f42441c445d70d8","location":{"path":"app/controllers/import/github_controller.rb","lines":{"begin":39,"end":58}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `GithubController` has 22 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"ab20d51ced6ac78aa49e0f6de0af0ac0","location":{"path":"app/controllers/import/github_controller.rb","lines":{"begin":1,"end":132}},"other_locations":[],"remediation_points":1400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_or_create_namespace` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2f80525d40e648a6d7ea2229b2cb74ae","location":{"path":"app/controllers/import/base_controller.rb","lines":{"begin":19,"end":31}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4d4ab07c39a5534d15b7443e37a7a235","location":{"path":"app/controllers/import/bitbucket_server_controller.rb","lines":{"begin":21,"end":45}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_import_params` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"da2d51f606c44590f847c4640444ac5b","location":{"path":"app/controllers/import/bitbucket_server_controller.rb","lines":{"begin":82,"end":90}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `callback` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3c9e710a6531242212414756cccfe915","location":{"path":"app/controllers/import/google_code_controller.rb","lines":{"begin":8,"end":33}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check_captcha` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"de5a9e305854d86a525c3be0d27eaf6d","location":{"path":"app/controllers/sessions_controller.rb","lines":{"begin":62,"end":78}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `auto_sign_in_with_provider` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f619928e9105c1311e52540b45817001","location":{"path":"app/controllers/sessions_controller.rb","lines":{"begin":167,"end":186}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `SessionsController` has 23 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"ca547cff8dfcb4f2bb32febd07c3a8b9","location":{"path":"app/controllers/sessions_controller.rb","lines":{"begin":3,"end":221}},"other_locations":[],"remediation_points":1500000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"44af899cceadececa5fb2d6343c42fb8","location":{"path":"app/controllers/registrations_controller.rb","lines":{"begin":16,"end":36}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `index` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"07120d763a5a37d3563d108da7618fc2","location":{"path":"app/controllers/snippets_controller.rb","lines":{"begin":30,"end":43}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `show` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"19fe68118b5b1cf2a9c1bfef20064546","location":{"path":"app/controllers/projects/wikis_controller.rb","lines":{"begin":20,"end":44}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4754ebccd6eb3ba307dee978dd12dde8","location":{"path":"app/controllers/projects/wikis_controller.rb","lines":{"begin":49,"end":65}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `merge_requests_controller.rb` has 265 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"ed0fbac1699084d99f2798b9ff54b642","location":{"path":"app/controllers/projects/merge_requests_controller.rb","lines":{"begin":1,"end":354}},"other_locations":[],"remediation_points":1416000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `show` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f6d989eecff0a311d8a4126b524b3d54","location":{"path":"app/controllers/projects/merge_requests_controller.rb","lines":{"begin":30,"end":76}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"043aac3b7446fd75e17405d975137865","location":{"path":"app/controllers/projects/merge_requests_controller.rb","lines":{"begin":123,"end":145}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ci_environments_status` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"46e544b6af20509fb22aa245f36e853b","location":{"path":"app/controllers/projects/merge_requests_controller.rb","lines":{"begin":204,"end":242}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `merge!` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5c7d0579b3b6ee7f277ab6932678009d","location":{"path":"app/controllers/projects/merge_requests_controller.rb","lines":{"begin":285,"end":319}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `MergeRequestsController` has 28 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"b062a438177acc72625495d3cbfaa77c","location":{"path":"app/controllers/projects/merge_requests_controller.rb","lines":{"begin":1,"end":354}},"other_locations":[],"remediation_points":2000000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `show` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e3c966096d2b12e85a670db7911707b8","location":{"path":"app/controllers/projects/merge_requests_controller.rb","lines":{"begin":30,"end":76}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `ci_environments_status` has 32 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e5b7f8ffd1531062cbb97dd163687759","location":{"path":"app/controllers/projects/merge_requests_controller.rb","lines":{"begin":204,"end":242}},"other_locations":[],"remediation_points":768000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `set_commits` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e6d95cec8ea5cbbfb499acf2d8c47bb9","location":{"path":"app/controllers/projects/commits_controller.rb","lines":{"begin":55,"end":69}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `archive` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"24b42f263d62dd73163666f08a5dab84","location":{"path":"app/controllers/projects/repositories_controller.rb","lines":{"begin":16,"end":28}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.","fingerprint":"48f2140ee4baa6d602990f3cd3a4e3bd","location":{"path":"app/controllers/projects/branches_controller.rb","lines":{"begin":52,"end":91}},"other_locations":[],"remediation_points":1350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `create` has 32 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"7b53414d925313d554a03dbde87a17b1","location":{"path":"app/controllers/projects/branches_controller.rb","lines":{"begin":52,"end":91}},"other_locations":[],"remediation_points":768000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `authenticate_user` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"aeab90a0370c820e728a3abfbc25b413","location":{"path":"app/controllers/projects/git_http_client_controller.rb","lines":{"begin":30,"end":59}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"59245dcb34ab6bc8f9698502300494c2","location":{"path":"app/controllers/projects/mirrors_controller.rb","lines":{"begin":15,"end":34}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `resolve` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"43486b045d4b1186318cfc251852d98f","location":{"path":"app/controllers/projects/notes_controller.rb","lines":{"begin":21,"end":36}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `unresolve` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"687318ee4eb16a33452c1f37b2c881a0","location":{"path":"app/controllers/projects/notes_controller.rb","lines":{"begin":38,"end":52}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"31c48fb29d257a60073d2b38d6e1cb4d","location":{"path":"app/controllers/projects/forks_controller.rb","lines":{"begin":39,"end":60}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `index` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"a118caadbf010e6edb81311d4382716d","location":{"path":"app/controllers/projects/pipelines_controller.rb","lines":{"begin":12,"end":44}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `show` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3abec97b27020fca915a84ac93bc67d6","location":{"path":"app/controllers/projects/imports_controller.rb","lines":{"begin":23,"end":39}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4da2f2dd27d4fd213b9f9f2f1a5cd41f","location":{"path":"app/controllers/projects/group_links_controller.rb","lines":{"begin":10,"end":22}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `service_test_response` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bca34a40fb26a815c60df6fd1e7dba51","location":{"path":"app/controllers/projects/services_controller.rb","lines":{"begin":36,"end":51}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `metrics` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"519adc463f2dd3de5cb94580d7f50383","location":{"path":"app/controllers/projects/deployments_controller.rb","lines":{"begin":15,"end":26}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `additional_metrics` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8c4f6fc892643a150dc844973ad4d976","location":{"path":"app/controllers/projects/deployments_controller.rb","lines":{"begin":28,"end":42}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `add_match_line` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"07c854b446b4087e5ee7892172f74ee7","location":{"path":"app/controllers/projects/blob_controller.rb","lines":{"begin":133,"end":149}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `blob` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b6f7a07f247048d784135038dce26244","location":{"path":"app/controllers/projects/blob_controller.rb","lines":{"begin":151,"end":165}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `editor_variables` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cfcc7b49429c64315cf6da835ec8bede","location":{"path":"app/controllers/projects/blob_controller.rb","lines":{"begin":192,"end":221}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `BlobController` has 21 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"c1e63be99f08c204815ffecf3dd4b66c","location":{"path":"app/controllers/projects/blob_controller.rb","lines":{"begin":2,"end":284}},"other_locations":[],"remediation_points":1300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `show` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"979438a3332d8f5f08717dc86472299b","location":{"path":"app/controllers/projects/tree_controller.rb","lines":{"begin":13,"end":47}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `show` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"806fa8153a8d1eb022e92afcd4601d91","location":{"path":"app/controllers/projects/tree_controller.rb","lines":{"begin":13,"end":47}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `raw` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e4f02dd301b3eeb842d85c37f20bae2e","location":{"path":"app/controllers/projects/jobs_controller.rb","lines":{"begin":126,"end":140}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `JobsController` has 21 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"6f969a368940183e4fce1acd28147f56","location":{"path":"app/controllers/projects/jobs_controller.rb","lines":{"begin":1,"end":189}},"other_locations":[],"remediation_points":1300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_merge_request_diff_compare` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"91ff93d1ec72c5ce3a260ca7bb0355a1","location":{"path":"app/controllers/projects/merge_requests/diffs_controller.rb","lines":{"begin":47,"end":73}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `download_objects!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b7b7331d3cec46b88359e5648ed2f9a2","location":{"path":"app/controllers/projects/lfs_api_controller.rb","lines":{"begin":52,"end":68}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `ClustersController` has 22 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"964ab09026b9448f413a95a5f6094e09","location":{"path":"app/controllers/projects/clusters_controller.rb","lines":{"begin":1,"end":222}},"other_locations":[],"remediation_points":1400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `move` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ae70ac550f5ace969dc34ad430e1a506","location":{"path":"app/controllers/projects/issues_controller.rb","lines":{"begin":95,"end":113}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `IssuesController` has 21 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"09358857b8d0a59b5b0f1efabe1eb312","location":{"path":"app/controllers/projects/issues_controller.rb","lines":{"begin":1,"end":251}},"other_locations":[],"remediation_points":1300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ensure_root_container_repository!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a31d8c82827ad31ea06cbe929b5bcb9e","location":{"path":"app/controllers/projects/registry/repositories_controller.rb","lines":{"begin":39,"end":47}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `get_keys` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e98b2fc794e033c8a48c3ee0becb6ada","location":{"path":"app/controllers/profiles/keys_controller.rb","lines":{"begin":36,"end":51}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `show` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7fac5d70e69bf9664ceb8d865c808fa2","location":{"path":"app/controllers/profiles/two_factor_auths_controller.rb","lines":{"begin":4,"end":40}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `show` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"053f896df50c58d1c5a7f874e0cf72c3","location":{"path":"app/controllers/profiles/two_factor_auths_controller.rb","lines":{"begin":4,"end":40}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `new` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"17c6af74d7968b2ff43b8ab9795a3e8a","location":{"path":"app/controllers/oauth/authorizations_controller.rb","lines":{"begin":6,"end":18}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `projects_controller.rb` has 328 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"ed6bb55f0c75667bbb0ec51d3c2664cf","location":{"path":"app/controllers/projects_controller.rb","lines":{"begin":3,"end":436}},"other_locations":[],"remediation_points":2323200,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `refs` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4477c3a607082b3d28aae895558786dd","location":{"path":"app/controllers/projects_controller.rb","lines":{"begin":241,"end":274}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `render_landing_page` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"278be4b0b74df103253b6e3205fb8a62","location":{"path":"app/controllers/projects_controller.rb","lines":{"begin":281,"end":298}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `ProjectsController` has 37 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"8090aa2673614a81e71589f7753eff30","location":{"path":"app/controllers/projects_controller.rb","lines":{"begin":3,"end":436}},"other_locations":[],"remediation_points":2900000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `project_params_attributes` has 37 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"2fdc2a483f87578ca62a61a751d113d7","location":{"path":"app/controllers/projects_controller.rb","lines":{"begin":331,"end":370}},"other_locations":[],"remediation_points":888000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `render_check_results` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ba1563517e02434643d39267194f528e","location":{"path":"app/controllers/health_controller.rb","lines":{"begin":39,"end":56}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `show` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f7c994d83ef228aa163c05179f06d2fd","location":{"path":"app/controllers/help_controller.rb","lines":{"begin":23,"end":57}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `clean_path_info` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5db34152511808356524914b64cf554b","location":{"path":"app/controllers/help_controller.rb","lines":{"begin":82,"end":107}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `application_controller.rb` has 341 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"f029769395ddcac92c2b8d2ca3b05b28","location":{"path":"app/controllers/application_controller.rb","lines":{"begin":3,"end":465}},"other_locations":[],"remediation_points":2510400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ldap_security_check` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"523cc3bb278c788d5848949c8c3dead7","location":{"path":"app/controllers/application_controller.rb","lines":{"begin":262,"end":272}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `enforce_terms!` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d14876e741b595110a644395f1230e14","location":{"path":"app/controllers/application_controller.rb","lines":{"begin":318,"end":339}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `ApplicationController` has 53 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"d63945970dce3ed1ef2b7634b214a4e9","location":{"path":"app/controllers/application_controller.rb","lines":{"begin":6,"end":465}},"other_locations":[],"remediation_points":4500000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `users_select_tag` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a108342e6dd48ba76731363b84661eaf","location":{"path":"app/helpers/selects_helper.rb","lines":{"begin":4,"end":26}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `users_select_data_attributes` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a8bad651f6dba343e0468fd0003dd784","location":{"path":"app/helpers/selects_helper.rb","lines":{"begin":74,"end":85}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `show_last_push_widget?` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2f5475afeb1122f3cfa25ac5955cbb27","location":{"path":"app/helpers/application_helper.rb","lines":{"begin":70,"end":86}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `visible_attributes` has 119 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"17f189b8fca7fc3b1f8a420a4bcbda19","location":{"path":"app/helpers/application_settings_helper.rb","lines":{"begin":146,"end":266}},"other_locations":[],"remediation_points":2856000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `toggle_award_url` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d69f1bff0994f6e3441f85f3f11d5bbe","location":{"path":"app/helpers/award_emoji_helper.rb","lines":{"begin":4,"end":17}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `page_gutter_class` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e65e525a4333beebdf83e031e767ef28","location":{"path":"app/helpers/nav_helper.rb","lines":{"begin":21,"end":39}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `get_header_links` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f16a544d112fad89ba71d21c6d5db6a3","location":{"path":"app/helpers/nav_helper.rb","lines":{"begin":55,"end":75}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `diff_match_line` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b869dd8c4eaef5740ed1f255a8f1fbf1","location":{"path":"app/helpers/diff_helper.rb","lines":{"begin":37,"end":57}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `parallel_diff_discussions` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8ca093d3db0a23030593eb120a3b43fd","location":{"path":"app/helpers/diff_helper.rb","lines":{"begin":69,"end":85}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `active_nav_link?` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a16ad6a8f13c9288d42920124805df7f","location":{"path":"app/helpers/tab_helper.rb","lines":{"begin":75,"end":104}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `user_status` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1c1f76f6143eff8d1d22a334a235fd12","location":{"path":"app/helpers/users_helper.rb","lines":{"begin":55,"end":71}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `namespaces_options` has a Cognitive Complexity of 20 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7abb679aa0b2ea21e01c24880d3bf77c","location":{"path":"app/helpers/namespaces_helper.rb","lines":{"begin":9,"end":45}},"other_locations":[],"remediation_points":1650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `options_for_group` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1e8e66fbb82bdafdc25047aa1524869b","location":{"path":"app/helpers/namespaces_helper.rb","lines":{"begin":71,"end":86}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `namespaces_options` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d1da7571d4cd35e4edcc84ea6e21ec17","location":{"path":"app/helpers/namespaces_helper.rb","lines":{"begin":9,"end":45}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `submodule_links` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"592e98a3099ca8bf38ff5b45bcb0dffb","location":{"path":"app/helpers/submodule_helper.rb","lines":{"begin":9,"end":47}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `submodule_links` has 32 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"c06e0c3ec596b1cdfd2de6f634cd6b86","location":{"path":"app/helpers/submodule_helper.rb","lines":{"begin":9,"end":47}},"other_locations":[],"remediation_points":768000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `remove_member_message` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bba34164517c03c61579ce09b9d98bb9","location":{"path":"app/helpers/members_helper.rb","lines":{"begin":4,"end":22}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `chunk_snippet` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"de18ceb549965f7ea862a8c0dc960bea","location":{"path":"app/helpers/snippets_helper.rb","lines":{"begin":64,"end":105}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `labels_filter_path` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9d39f54cf4cda251c101014a6f4c7244","location":{"path":"app/helpers/labels_helper.rb","lines":{"begin":134,"end":149}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `label_status_tooltip` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"64c9182b39df4fa2de87d8d55c2d2cbc","location":{"path":"app/helpers/labels_helper.rb","lines":{"begin":216,"end":222}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `event_feed_title` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0ae609cca64a854e8b692dd72db01e13","location":{"path":"app/helpers/events_helper.rb","lines":{"begin":78,"end":102}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `event_feed_url` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f99a06ad5d757ef02870ca23d95676bc","location":{"path":"app/helpers/events_helper.rb","lines":{"begin":104,"end":122}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `push_event_feed_url` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0a4c5fb26657f91ff277b7ef0d13c32d","location":{"path":"app/helpers/events_helper.rb","lines":{"begin":124,"end":138}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `discussion_path` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f8fcc6a280f6df449c018435d3f92438","location":{"path":"app/helpers/notes_helper.rb","lines":{"begin":77,"end":92}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `clipboard_button` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c29991b281e121dc435a356a600e4e1e","location":{"path":"app/helpers/button_helper.rb","lines":{"begin":22,"end":59}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `dropdown_item_with_description` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8d5076e20cc132338854d62653cd1df8","location":{"path":"app/helpers/button_helper.rb","lines":{"begin":88,"end":97}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `clipboard_button` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"97e66a0f68fa32b384f734ea55e5e044","location":{"path":"app/helpers/button_helper.rb","lines":{"begin":22,"end":59}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `dropdown_tag` has a Cognitive Complexity of 26 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4267997c227f431d5c46888b32748d75","location":{"path":"app/helpers/dropdowns_helper.rb","lines":{"begin":4,"end":41}},"other_locations":[],"remediation_points":2250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `dropdown_toggle` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7a5dbb87125de6801bd8e3f421ea8d82","location":{"path":"app/helpers/dropdowns_helper.rb","lines":{"begin":43,"end":50}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `dropdown_tag` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"5365287d8861b3ecac3eb84417018a2b","location":{"path":"app/helpers/dropdowns_helper.rb","lines":{"begin":4,"end":41}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `sprite_icon` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b288e014419e55f1aa86631629c79889","location":{"path":"app/helpers/icons_helper.rb","lines":{"begin":44,"end":56}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `file_type_icon_class` has 37 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"2ddea2c28588a85667f5b5399507ff46","location":{"path":"app/helpers/icons_helper.rb","lines":{"begin":109,"end":151}},"other_locations":[],"remediation_points":888000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `commit_action_link` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8657821ad7a6c0ec4e6398a6aee36f9c","location":{"path":"app/helpers/commits_helper.rb","lines":{"begin":161,"end":181}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `todo_target_path` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d03a4561eaff01e66db146018ad0c969","location":{"path":"app/helpers/todos_helper.rb","lines":{"begin":39,"end":54}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `todo_due_date` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"db4163fb6bc594c0c7f5188d10a94d61","location":{"path":"app/helpers/todos_helper.rb","lines":{"begin":141,"end":160}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `url_for_issue` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b77357907fc65f32d6adebdec5c99e74","location":{"path":"app/helpers/issues_helper.rb","lines":{"begin":18,"end":32}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `path_breadcrumbs` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.","fingerprint":"309df9fc4689b2f5fb71f6534a5efd03","location":{"path":"app/helpers/tree_helper.rb","lines":{"begin":105,"end":121}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `edit_fork_button_tag` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"2b6c57301b36358581b2491652866c44","location":{"path":"app/helpers/blob_helper.rb","lines":{"begin":302,"end":302}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `edit_button_tag` has 6 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"723d5e25e5499349ff424fa5046927f1","location":{"path":"app/helpers/blob_helper.rb","lines":{"begin":318,"end":318}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `modify_file_button` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4b6850840a5430937e584328e518311d","location":{"path":"app/helpers/blob_helper.rb","lines":{"begin":51,"end":69}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `blob_raw_url` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b8466fa1a816b27c99638fe228016597","location":{"path":"app/helpers/blob_helper.rb","lines":{"begin":119,"end":131}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `edit_button_tag` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"93c71a130633cfcebe91bcda32798413","location":{"path":"app/helpers/blob_helper.rb","lines":{"begin":318,"end":327}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `projects_helper.rb` has 438 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"a3d8d9970bc1e29fb6cf0845a8963a35","location":{"path":"app/helpers/projects_helper.rb","lines":{"begin":3,"end":555}},"other_locations":[],"remediation_points":3907200,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `link_to_member` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"92e14b6f78cd2026911faaf78829ba53","location":{"path":"app/helpers/projects_helper.rb","lines":{"begin":49,"end":73}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `project_title` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2ebe86c74e8aaea024bada46109f4e9a","location":{"path":"app/helpers/projects_helper.rb","lines":{"begin":75,"end":93}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `get_project_nav_tabs` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8c4c2cc0402179e15ec7cff7acf71f67","location":{"path":"app/helpers/projects_helper.rb","lines":{"begin":266,"end":300}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `disallowed_project_visibility_level_description` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cde9c43b6e33582ebc01be9dcc6b0cc6","location":{"path":"app/helpers/visibility_level_helper.rb","lines":{"begin":84,"end":102}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `disallowed_group_visibility_level_description` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c964b53a81590235d500b10d64921ffc","location":{"path":"app/helpers/visibility_level_helper.rb","lines":{"begin":106,"end":128}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `webpack_controller_bundle_tags` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d1915400e4cdf8101f80cf09fcaa41a8","location":{"path":"app/helpers/webpack_helper.rb","lines":{"begin":8,"end":34}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `webpack_entrypoint_paths` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2059057b0a4c8c172104d3a69b6d33a1","location":{"path":"app/helpers/webpack_helper.rb","lines":{"begin":36,"end":57}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `webpack_public_host` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5187936b625c537cc83fe49f935245ac","location":{"path":"app/helpers/webpack_helper.rb","lines":{"begin":59,"end":68}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `milestone_class_for_state` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9f98077e679dcc8bdc4adbc9fc03e111","location":{"path":"app/helpers/milestones_helper.rb","lines":{"begin":71,"end":79}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `milestone_time_for` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7e9f0e967afa3a497b80340cf11b095e","location":{"path":"app/helpers/milestones_helper.rb","lines":{"begin":122,"end":144}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `milestone_date_range` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4843496f3de8ac4a890951c97d469064","location":{"path":"app/helpers/milestones_helper.rb","lines":{"begin":181,"end":197}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `share_with_group_lock_help_text` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ff00375205d66e384de0e3f33ec7d0d7","location":{"path":"app/helpers/groups_helper.rb","lines":{"begin":96,"end":108}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `group_title_link` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"98657f045e5336754e6eae56e789418f","location":{"path":"app/helpers/groups_helper.rb","lines":{"begin":141,"end":146}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `ci_icon_for_status` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"7c3efeba2a30134025307449a05b2f77","location":{"path":"app/helpers/ci_status_helper.rb","lines":{"begin":61,"end":91}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `sorting_helper.rb` has 302 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"01923968066dde38b1a1f78557e3aa84","location":{"path":"app/helpers/sorting_helper.rb","lines":{"begin":3,"end":382}},"other_locations":[],"remediation_points":1948800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `link_to_html` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7a4d4827db929df3ce6e16fb4d0b9522","location":{"path":"app/helpers/markup_helper.rb","lines":{"begin":45,"end":69}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `first_line_in_markdown` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a45a636cd6cfd627a9b9cb575482d665","location":{"path":"app/helpers/markup_helper.rb","lines":{"begin":75,"end":92}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `render_wiki_content` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f59f576a0875d3e1b3150c35d4244f1f","location":{"path":"app/helpers/markup_helper.rb","lines":{"begin":124,"end":148}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `markup_unsafe` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0f734634b0f84e17e369619da7591cc9","location":{"path":"app/helpers/markup_helper.rb","lines":{"begin":150,"end":166}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `truncate_visible` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7de1befc5006684b8aac21b36b5a2eee","location":{"path":"app/helpers/markup_helper.rb","lines":{"begin":198,"end":229}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `truncate_if_block` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c65f7114f8bc0ff0c40ba17a721dc4e7","location":{"path":"app/helpers/markup_helper.rb","lines":{"begin":234,"end":243}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `issuables_helper.rb` has 354 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"b0534175d956cb265e089add14a703e9","location":{"path":"app/helpers/issuables_helper.rb","lines":{"begin":3,"end":436}},"other_locations":[],"remediation_points":2697600,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `multi_label_name` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f6f2aa839b3380e86377b58408fcfa15","location":{"path":"app/helpers/issuables_helper.rb","lines":{"begin":40,"end":51}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `issuable_labels_tooltip` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6ed7317a98ede04c4a1cde7f6af1871c","location":{"path":"app/helpers/issuables_helper.rb","lines":{"begin":208,"end":219}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `issuable_todo_button_data` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"382bc77616cd06810f3d2112c8460240","location":{"path":"app/helpers/issuables_helper.rb","lines":{"begin":381,"end":395}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `project_policy.rb` has 333 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"61f7a144aa6cdf7e965d3130c52aacc3","location":{"path":"app/policies/project_policy.rb","lines":{"begin":3,"end":434}},"other_locations":[],"remediation_points":2395200,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `remaining_days_in_words` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"db6e21e4091f24644db9efa04f03f5f2","location":{"path":"app/serializers/entity_date_helper.rb","lines":{"begin":47,"end":70}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `build_metrics` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fe3dd187f741789f765000b6e08c77bf","location":{"path":"app/serializers/merge_request_widget_entity.rb","lines":{"begin":252,"end":260}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `reassigned_issue_email` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"3e2a4c8e9b4b5584a258cdc21bf1779b","location":{"path":"app/mailers/emails/issues.rb","lines":{"begin":23,"end":23}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `relabeled_issue_email` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"eccd65b7da076099478491ce5ffd1bcd","location":{"path":"app/mailers/emails/issues.rb","lines":{"begin":40,"end":40}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `issue_status_changed_email` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"8d347450b4f4fa27b8d847a4e282b8f2","location":{"path":"app/mailers/emails/issues.rb","lines":{"begin":48,"end":48}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `issue_moved_email` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"a1fb6126b5a4051ae7af19c5fd1ea1ef","location":{"path":"app/mailers/emails/issues.rb","lines":{"begin":56,"end":56}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `reassigned_merge_request_email` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"8bd5f4e62d9db3075aa99840c56f1dc3","location":{"path":"app/mailers/emails/merge_requests.rb","lines":{"begin":26,"end":26}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `relabeled_merge_request_email` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"e4fbe646413819b74d51d11132d24f75","location":{"path":"app/mailers/emails/merge_requests.rb","lines":{"begin":34,"end":34}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `merge_request_status_email` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"c39ada600f20e34ac7bc271aaa17a489","location":{"path":"app/mailers/emails/merge_requests.rb","lines":{"begin":55,"end":55}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `NotifyPreview` has 26 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"0ad28a90a453e7f0892e2fa27ca7dd74","location":{"path":"app/mailers/previews/notify_preview.rb","lines":{"begin":3,"end":176}},"other_locations":[],"remediation_points":1800000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `perform` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"ae09aa8f81cc51243569392f706dea8b","location":{"path":"app/workers/irker_worker.rb","lines":{"begin":9,"end":9}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `send_branch_updates` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"2efa6a64aab186fa6e22c887b82c2da8","location":{"path":"app/workers/irker_worker.rb","lines":{"begin":61,"end":61}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `send_commits` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"bda830a07ed575bd9c98deb67e6efa84","location":{"path":"app/workers/irker_worker.rb","lines":{"begin":84,"end":84}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `send_commits_count` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"fe8fcb808f2da3f31174330b3b8e8d2f","location":{"path":"app/workers/irker_worker.rb","lines":{"begin":113,"end":113}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `process_commit_message` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"0cbd58f4d824762ee08cbe8394a955be","location":{"path":"app/workers/process_commit_worker.rb","lines":{"begin":36,"end":36}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `close_issues` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"0ddd5587d752e08728eca56e8b842057","location":{"path":"app/workers/process_commit_worker.rb","lines":{"begin":45,"end":45}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"00fda5cf89ca2d06f32e73e5948d2d96","location":{"path":"app/workers/process_commit_worker.rb","lines":{"begin":18,"end":33}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b2d21175bd49ce0560d00b41a5d4b105","location":{"path":"app/workers/emails_on_push_worker.rb","lines":{"begin":8,"end":78}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `perform` has 58 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"21b177b9c8aab67c1dd18ac4736d07e0","location":{"path":"app/workers/emails_on_push_worker.rb","lines":{"begin":8,"end":78}},"other_locations":[],"remediation_points":1392000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bc6bfd63748977ec6b13ad1bcd32f3e0","location":{"path":"app/workers/repository_import_worker.rb","lines":{"begin":9,"end":30}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `perform` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"ac08a8c45d58e80f3f9f0e821af8c678","location":{"path":"app/workers/create_pipeline_worker.rb","lines":{"begin":9,"end":9}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `sanity_check!` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1e3c40adf8befa842892c33a2ef33ef6","location":{"path":"app/workers/object_storage/migrate_uploads_worker.rb","lines":{"begin":71,"end":81}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5767377fdd633dc52d45bb5edaf792ad","location":{"path":"app/workers/object_storage/background_move_worker.rb","lines":{"begin":10,"end":21}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"873b9f464ca14204612b7168bd6cd51a","location":{"path":"app/workers/repository_update_remote_mirror_worker.rb","lines":{"begin":18,"end":42}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform_repository_checks` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c569f618db6dab9061d1c9a4fbd00161","location":{"path":"app/workers/repository_check/batch_worker.rb","lines":{"begin":34,"end":49}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `perform` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"fb73a85eb279eea1e916d045d1752c0b","location":{"path":"app/workers/update_merge_requests_worker.rb","lines":{"begin":9,"end":9}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9c731770fbe34f3e5817178bf1c1c9c2","location":{"path":"app/workers/post_receive.rb","lines":{"begin":6,"end":25}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `process_project_changes` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"dcfa21f106c7ddc7d59ef6a69ed225a5","location":{"path":"app/workers/post_receive.rb","lines":{"begin":29,"end":52}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"90ee5638594e38ee879df3d7b6670837","location":{"path":"app/workers/git_garbage_collect_worker.rb","lines":{"begin":11,"end":36}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c91f311ed659a5cf472f55294f93721e","location":{"path":"app/workers/project_migrate_hashed_storage_worker.rb","lines":{"begin":9,"end":22}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `handle_failure` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ea43d53b1207bf05223f6e3378b7622f","location":{"path":"app/workers/email_receiver_worker.rb","lines":{"begin":18,"end":51}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fb063b327d5645323a0adeb4a475ab88","location":{"path":"app/workers/create_gpg_signature_worker.rb","lines":{"begin":7,"end":29}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"90474c7bc130abea03bc8b0c1a6d72e3","location":{"path":"app/finders/concerns/finder_with_cross_project_access.rb","lines":{"begin":20,"end":32}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `by_due_date` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a7cd7e8d0b5f825ced9cce3fd0921f8b","location":{"path":"app/finders/issues_finder.rb","lines":{"begin":74,"end":90}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `all_groups` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9450c1e48d60393e153d88597dff24f6","location":{"path":"app/finders/groups_finder.rb","lines":{"begin":44,"end":54}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"be7b1d323697075c6c41a353ae096f3b","location":{"path":"app/finders/environments_finder.rb","lines":{"begin":11,"end":47}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `execute` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"caa84d8a4abdc89470c9734cc20f668b","location":{"path":"app/finders/environments_finder.rb","lines":{"begin":11,"end":47}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `issuable_finder.rb` has 326 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"a0da380c5a294186d2f8b47c7dbbefc2","location":{"path":"app/finders/issuable_finder.rb","lines":{"begin":30,"end":487}},"other_locations":[],"remediation_points":2294400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `milestones` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"10f2a889ce05a62411b22bd93f5cc5fb","location":{"path":"app/finders/issuable_finder.rb","lines":{"begin":195,"end":214}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `by_milestone` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4b5705f5f9e383902a98d61545a61a9f","location":{"path":"app/finders/issuable_finder.rb","lines":{"begin":432,"end":447}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `IssuableFinder` has 47 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"863953770234ecee39c26f4a376fd96f","location":{"path":"app/finders/issuable_finder.rb","lines":{"begin":30,"end":487}},"other_locations":[],"remediation_points":3900000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `target` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"10931dd4f683d88c8163ae49517c9ab8","location":{"path":"app/finders/notes_finder.rb","lines":{"begin":30,"end":46}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `by_archived` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8a0163155f7d44abda52664f1aeec277","location":{"path":"app/finders/projects_finder.rb","lines":{"begin":153,"end":167}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `label_ids` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8efea533e852a1ae66e134916ddee3ed","location":{"path":"app/finders/labels_finder.rb","lines":{"begin":30,"end":59}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `project` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"dcae9d4bea82b824bf3281aee3585c28","location":{"path":"app/finders/labels_finder.rb","lines":{"begin":138,"end":149}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1b10747a7f6c51031eb2aec28a84ab64","location":{"path":"app/finders/autocomplete/users_finder.rb","lines":{"begin":27,"end":40}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `distinct_on` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"684bb4005fc7ad31c75a6725f7cc9bb4","location":{"path":"app/finders/members_finder.rb","lines":{"begin":38,"end":71}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `TodosFinder` has 23 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"bda816f835a5b1d9557e5018542c8d40","location":{"path":"app/finders/todos_finder.rb","lines":{"begin":17,"end":195}},"other_locations":[],"remediation_points":1500000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_block` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2764263922dc65629ac261e7b22a4aaa","location":{"path":"rubocop/cop/avoid_break_from_strong_memoize.rb","lines":{"begin":26,"end":37}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d3d826dd296b41ed52a3d279711f08c1","location":{"path":"rubocop/cop/migration/reversible_add_column_with_default.rb","lines":{"begin":22,"end":31}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7c2b352ab3b2b95bfd273eed48b0abde","location":{"path":"rubocop/cop/migration/update_column_in_batches.rb","lines":{"begin":14,"end":23}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1ca44c40f4350122f1d014c4832b0903","location":{"path":"rubocop/cop/migration/update_large_table.rb","lines":{"begin":52,"end":64}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"259f2282957271f68f06320d6e9c9304","location":{"path":"rubocop/cop/migration/add_reference.rb","lines":{"begin":13,"end":31}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_def` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1eade1345b7999362c94ffdd69c9abe5","location":{"path":"rubocop/cop/migration/add_index.rb","lines":{"begin":12,"end":32}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2957b0117eb58305df4d1c4f628f6e22","location":{"path":"rubocop/cop/migration/add_concurrent_index.rb","lines":{"begin":14,"end":26}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a265e4d95fda1c51004e6b280d4faa62","location":{"path":"rubocop/cop/migration/hash_index.rb","lines":{"begin":16,"end":35}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4642fe2fb8caa127e076b834679b38c7","location":{"path":"rubocop/cop/migration/add_column.rb","lines":{"begin":16,"end":35}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"53f08cb9ea1aded1c4867d5ec20513e1","location":{"path":"rubocop/cop/migration/datetime.rb","lines":{"begin":26,"end":38}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_def` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"33c826c076b3d604191dc285ae1c197f","location":{"path":"rubocop/cop/migration/remove_column.rb","lines":{"begin":13,"end":26}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5f6ab7199493398668d2f7fb4ae05a45","location":{"path":"rubocop/cop/migration/safer_boolean_column.rb","lines":{"begin":34,"end":58}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5d3a133fdd5733e12d588c313215673c","location":{"path":"rubocop/cop/migration/remove_concurrent_index.rb","lines":{"begin":14,"end":21}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_block` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"60e29c1a431f0815adefc57a3daa815a","location":{"path":"rubocop/cop/avoid_return_from_blocks.rb","lines":{"begin":28,"end":39}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `parent_blocks` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bf710c1583fafaba0fb4a8d7507199ca","location":{"path":"rubocop/cop/avoid_return_from_blocks.rb","lines":{"begin":55,"end":65}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_class` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ac6a847fc937c343670c7deedadaa245","location":{"path":"rubocop/cop/code_reuse/presenter.rb","lines":{"begin":20,"end":37}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check_all_send_nodes` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"86036271965f234231993a45bdebb14d","location":{"path":"rubocop/cop/code_reuse/worker.rb","lines":{"begin":31,"end":46}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_class` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3468ff5bbfe02d60179b461cee64dcd2","location":{"path":"rubocop/cop/code_reuse/serializer.rb","lines":{"begin":20,"end":37}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f6cd42fcfb267a05384d5c43432583d9","location":{"path":"rubocop/cop/code_reuse/active_record.rb","lines":{"begin":91,"end":107}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_if` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c31d398a0e65e6ad5bf772ffbe612d27","location":{"path":"rubocop/cop/line_break_around_conditional_block.rb","lines":{"begin":50,"end":58}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `autocorrect` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6790fd3dafb4732df58459faaaee4a86","location":{"path":"rubocop/cop/line_break_around_conditional_block.rb","lines":{"begin":60,"end":71}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `on_send` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"83f3ce0952418f2d18756e7f01d3f37e","location":{"path":"rubocop/cop/project_path_helper.rb","lines":{"begin":10,"end":21}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this method.","location":{"path":"rubocop/cop/project_path_helper.rb","lines":{"begin":18,"end":18}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"16132947f8a9d308be379b39b9a5d3e6"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `perform` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"00f629cf63f892026e01afc3ea1f7651","location":{"path":"qa/qa/scenario/test/sanity/selectors.rb","lines":{"begin":10,"end":49}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `configure!` has 44 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"024a3eec035772b027f91725d6217728","location":{"path":"qa/qa/runtime/browser.rb","lines":{"begin":34,"end":100}},"other_locations":[],"remediation_points":1056000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b5bc236abe446161666817def77f8ddf","location":{"path":"qa/qa/specs/runner.rb","lines":{"begin":16,"end":34}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `fabricate!` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d6f04a78d84c195424103bc166feb302","location":{"path":"qa/qa/factory/repository/push.rb","lines":{"begin":33,"end":75}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `fabricate!` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"0619d32fe486fa8cd5d32a978dbba114","location":{"path":"qa/qa/factory/repository/push.rb","lines":{"begin":33,"end":75}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `fabricate!` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3806fbbd18c151ecd8c0b6a777c6eafd","location":{"path":"qa/qa/factory/resource/kubernetes_cluster.rb","lines":{"begin":16,"end":55}},"other_locations":[],"remediation_points":1550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `fabricate!` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"9a68225ad27f7d73dc48ba5138c97cc4","location":{"path":"qa/qa/factory/resource/kubernetes_cluster.rb","lines":{"begin":16,"end":55}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `fabricate!` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"455a528019b993508c00252ac2356d13","location":{"path":"qa/qa/factory/resource/branch.rb","lines":{"begin":19,"end":73}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `fabricate!` has 40 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8c20ae555f1495416940ef8126556a4b","location":{"path":"qa/qa/factory/resource/branch.rb","lines":{"begin":19,"end":73}},"other_locations":[],"remediation_points":960000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `sign_in_using_credentials` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cf2fda789a392298297395e24cd69833","location":{"path":"qa/qa/page/main/login.rb","lines":{"begin":43,"end":60}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `expand_section` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"403c8764d8456908ed9eeef4e0044633","location":{"path":"qa/qa/page/settings/common.rb","lines":{"begin":8,"end":19}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Repository` has 21 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"1f47da27b9c8ebadc35daab5ecdfacbd","location":{"path":"qa/qa/git/repository.rb","lines":{"begin":7,"end":126}},"other_locations":[],"remediation_points":1300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Base` has 36 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"e990b96533b4c82f0e6c7290f79ea42e","location":{"path":"lib/declarative_policy/base.rb","lines":{"begin":2,"end":330}},"other_locations":[],"remediation_points":2800000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"return_statements","content":{"body":""},"description":"Avoid too many `return` statements within this method.","location":{"path":"lib/declarative_policy/condition.rb","lines":{"begin":72,"end":72}},"other_locations":[],"remediation_points":300000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"453a21507c9d4e6b09338d4ea74a2cb6"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `run` has a Cognitive Complexity of 29 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9109da1712e5180769d6dee88e01ab4b","location":{"path":"lib/declarative_policy/runner.rb","lines":{"begin":76,"end":108}},"other_locations":[],"remediation_points":2550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `steps_by_score` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c728761c8bd550bb676d5ac29cc54f2c","location":{"path":"lib/declarative_policy/runner.rb","lines":{"begin":130,"end":179}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `steps_by_score` has 32 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"fda7518f9492e598aeaa14084a16fcad","location":{"path":"lib/declarative_policy/runner.rb","lines":{"begin":130,"end":179}},"other_locations":[],"remediation_points":768000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `flattened` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"938e199f4acb9d23e67b122acf7df125","location":{"path":"lib/declarative_policy/step.rb","lines":{"begin":51,"end":76}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e8ff80c1013b45a887d5b05aebda0a6d","location":{"path":"lib/mattermost/session.rb","lines":{"begin":100,"end":112}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Session` has 21 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"b6bc576e4f2b4e33eeb24fbd9a4756e0","location":{"path":"lib/mattermost/session.rb","lines":{"begin":23,"end":185}},"other_locations":[],"remediation_points":1300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `from_params` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8708d87b45e03dda5cf801ba3c8b4ae8","location":{"path":"lib/uploaded_file.rb","lines":{"begin":31,"end":50}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `object_link_commit` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4d75625eef2a0bac64c81e6a99b470c7","location":{"path":"lib/banzai/filter/merge_request_reference_filter.rb","lines":{"begin":66,"end":77}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e4a7914b17bf2c4568c886ae0510580a","location":{"path":"lib/banzai/filter/issuable_state_filter.rb","lines":{"begin":13,"end":29}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c21517ccaabda5807f34857c92c02c20","location":{"path":"lib/banzai/filter/emoji_filter.rb","lines":{"begin":11,"end":26}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5b2ff3f6e59ccd27a3e8df6e92792289","location":{"path":"lib/banzai/filter/gollum_tags_filter.rb","lines":{"begin":64,"end":87}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `build_relative_path` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1fc007ca58af10483040ad6c3acafc7b","location":{"path":"lib/banzai/filter/relative_link_filter.rb","lines":{"begin":124,"end":140}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0d11bc0fb07cb93de54e50ca187a470c","location":{"path":"lib/banzai/filter/inline_diff_filter.rb","lines":{"begin":8,"end":20}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"229de514760543ff1eae907b8931ae4a","location":{"path":"lib/banzai/filter/spaced_link_filter.rb","lines":{"begin":44,"end":60}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 28 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f2adb2780e68c3edecbf3243959331bc","location":{"path":"lib/banzai/filter/abstract_reference_filter.rb","lines":{"begin":99,"end":148}},"other_locations":[],"remediation_points":2450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `object_link_filter` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.","fingerprint":"756d39528c96fdea1b53fc02cd2f1b54","location":{"path":"lib/banzai/filter/abstract_reference_filter.rb","lines":{"begin":161,"end":204}},"other_locations":[],"remediation_points":1550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `AbstractReferenceFilter` has 31 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"bf8591fc0f7db3df80a7dd6204670584","location":{"path":"lib/banzai/filter/abstract_reference_filter.rb","lines":{"begin":7,"end":359}},"other_locations":[],"remediation_points":2300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `call` has 36 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"158c3e7c7c151f7461c460b58c8eb7fb","location":{"path":"lib/banzai/filter/abstract_reference_filter.rb","lines":{"begin":99,"end":148}},"other_locations":[],"remediation_points":864000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `object_link_filter` has 35 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"cce139d64bf42a05629c35156d98c24b","location":{"path":"lib/banzai/filter/abstract_reference_filter.rb","lines":{"begin":161,"end":204}},"other_locations":[],"remediation_points":840000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f009def1da08a1c0910203aacb888646","location":{"path":"lib/banzai/filter/external_issue_reference_filter.rb","lines":{"begin":30,"end":55}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e19e0b6d7127e7778817e23a5c2202a6","location":{"path":"lib/banzai/filter/external_link_filter.rb","lines":{"begin":9,"end":23}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `highlight_node` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9dc3f1809c63dc39b3c7b301b2c8b0a8","location":{"path":"lib/banzai/filter/syntax_highlight_filter.rb","lines":{"begin":19,"end":53}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0228be52a5d55a59f39144f901ba3de8","location":{"path":"lib/banzai/filter/user_reference_filter.rb","lines":{"begin":28,"end":51}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `user_link_filter` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e1a29dd5f3f29173136fead3b7feacff","location":{"path":"lib/banzai/filter/user_reference_filter.rb","lines":{"begin":61,"end":75}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c56c41f7c09938431d527c9d84ee464c","location":{"path":"lib/banzai/filter/project_reference_filter.rb","lines":{"begin":26,"end":47}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"23c5be476e6cfabb3f50b8995a21481e","location":{"path":"lib/banzai/filter/autolink_filter.rb","lines":{"begin":51,"end":67}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `autolink_match` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"63ef57bb6d5d4556a6d292a19e1eb7aa","location":{"path":"lib/banzai/filter/autolink_filter.rb","lines":{"begin":79,"end":116}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `remove_unsafe_links` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"578c89c0c495a9f9912ae5425a9baf35","location":{"path":"lib/banzai/filter/sanitization_filter.rb","lines":{"begin":64,"end":91}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `remove_unsafe_table_style` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"66ae1f700aade40e1b93928de1809522","location":{"path":"lib/banzai/filter/sanitization_filter.rb","lines":{"begin":101,"end":114}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `process_link_attr` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fe0e7a59ae612e2391ff8956ece5a37e","location":{"path":"lib/banzai/filter/absolute_link_filter.rb","lines":{"begin":21,"end":29}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"91ceeacf382258ddf799f7e8df323086","location":{"path":"lib/banzai/filter/commit_trailers_filter.rb","lines":{"begin":29,"end":43}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `link_to_user` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"2cae91969679378626a39ee56d06c378","location":{"path":"lib/banzai/filter/commit_trailers_filter.rb","lines":{"begin":81,"end":115}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"complex_logic","content":{"body":""},"description":"Consider simplifying this complex logical expression.","location":{"path":"lib/banzai/filter/math_filter.rb","lines":{"begin":27,"end":36}},"other_locations":[],"remediation_points":400000,"severity":"major","type":"issue","engine_name":"structure","fingerprint":"c132e79b1733f1ef2e4fba2f07f29935"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"88347949c1ff362bde8f0c3f4fe03dc2","location":{"path":"lib/banzai/filter/table_of_contents_filter.rb","lines":{"begin":21,"end":52}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_parent` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b3d0438ee7565d4ad0316ba6f05a9baf","location":{"path":"lib/banzai/filter/table_of_contents_filter.rb","lines":{"begin":102,"end":115}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `nodes_visible_to_user` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c851d1fc927d9308df218d02f42fde99","location":{"path":"lib/banzai/reference_parser/user_parser.rb","lines":{"begin":25,"end":48}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `nodes_user_can_reference` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bfa5e9970213f7ef43f0e22780710fb3","location":{"path":"lib/banzai/reference_parser/user_parser.rb","lines":{"begin":64,"end":87}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `grouped_objects_for_nodes` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"56d2576cd012b6e37a94c3cac839a5fd","location":{"path":"lib/banzai/reference_parser/base_parser.rb","lines":{"begin":134,"end":147}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `BaseParser` has 21 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"3b65d4312cddf7542466b907246e6568","location":{"path":"lib/banzai/reference_parser/base_parser.rb","lines":{"begin":34,"end":255}},"other_locations":[],"remediation_points":1300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `cache_collection_render` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"86955f7d246e45efcedaab9abac1b39e","location":{"path":"lib/banzai/renderer.rb","lines":{"begin":76,"end":103}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `filter_nodes_at_beginning` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"575e435b81c0cb589731931503f3f7ef","location":{"path":"lib/banzai/querying.rb","lines":{"begin":45,"end":64}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `initialize` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a1435f7fb2485ddc0076596fa851b5e1","location":{"path":"lib/file_size_validator.rb","lines":{"begin":8,"end":17}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check_validity!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4c0eea681bcd3ea73ab99bdeb9992c52","location":{"path":"lib/file_size_validator.rb","lines":{"begin":19,"end":33}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_each` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4ad97d789c0a7bb2c49b85af370fa1cb","location":{"path":"lib/file_size_validator.rb","lines":{"begin":36,"end":65}},"other_locations":[],"remediation_points":1550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `projects.rb` has 419 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"de2195ccf1e60f3a00e250b69809f314","location":{"path":"lib/api/projects.rb","lines":{"begin":1,"end":513}},"other_locations":[],"remediation_points":3633600,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `apply_filters` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3d37733ec13b40a7294cb95cca30d7c0","location":{"path":"lib/api/projects.rb","lines":{"begin":21,"end":27}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `entities.rb` has 1174 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"14ca44a7292f309674c3e6bf5668d68a","location":{"path":"lib/api/entities.rb","lines":{"begin":1,"end":1469}},"other_locations":[],"remediation_points":14505600,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create_note` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5bdd6d096b03346b779bae1a47c5df19","location":{"path":"lib/api/helpers/notes_helpers.rb","lines":{"begin":89,"end":101}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_job!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9c2df515fd3e4e567e5c43ce34122ebc","location":{"path":"lib/api/helpers/runner.rb","lines":{"begin":34,"end":42}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `filter_runners` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8fe97bd7f7ff40f6f347505c5401de55","location":{"path":"lib/api/runners.rb","lines":{"begin":163,"end":176}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `authenticate_show_runner!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"678c4afecdf242daef2608fb52f16121","location":{"path":"lib/api/runners.rb","lines":{"begin":184,"end":188}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `authenticate_delete_runner!` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"229dc324c7b4cd0754d3f429d0efa489","location":{"path":"lib/api/runners.rb","lines":{"begin":196,"end":201}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `authenticate_enable_runner!` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fdde7223cb136c51a33619a48b7e10e9","location":{"path":"lib/api/runners.rb","lines":{"begin":203,"end":210}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `issues.rb` has 266 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"49d5eae8f5a9b7ef20af4a226580c9fb","location":{"path":"lib/api/issues.rb","lines":{"begin":1,"end":332}},"other_locations":[],"remediation_points":1430400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `filter_builds` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b03e3d209e73511af90d54684b559d3a","location":{"path":"lib/api/jobs.rb","lines":{"begin":176,"end":185}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `list_milestones_for` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8e051bd18be2af43510bfc934382e630","location":{"path":"lib/api/milestone_responses.rb","lines":{"begin":30,"end":37}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `assign_file_vars!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4be22423f2ecad963ae6b37b42f43510","location":{"path":"lib/api/files.rb","lines":{"begin":25,"end":36}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `assign_blob_vars!` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e73cbaada1130cabaf0dfa4c08f70656","location":{"path":"lib/api/repositories.rb","lines":{"begin":22,"end":35}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `services.rb` has 836 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"68db3252fd8eaa9d286783dcd5099377","location":{"path":"lib/api/services.rb","lines":{"begin":2,"end":864}},"other_locations":[],"remediation_points":9638400,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `runner.rb` has 266 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"1e11149dffed6dc2e322c584638327bf","location":{"path":"lib/api/runner.rb","lines":{"begin":1,"end":316}},"other_locations":[],"remediation_points":1430400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `merge_requests.rb` has 322 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"3de9fe3d6f81552276ac816a0f052fb7","location":{"path":"lib/api/merge_requests.rb","lines":{"begin":1,"end":398}},"other_locations":[],"remediation_points":2236800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `handle_merge_request_errors!` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4b0820e7ae582cfc463b18401dc7dfd6","location":{"path":"lib/api/merge_requests.rb","lines":{"begin":145,"end":159}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `helpers.rb` has 381 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"5db68c679d275d3963e0f9660988e103","location":{"path":"lib/api/helpers.rb","lines":{"begin":1,"end":533}},"other_locations":[],"remediation_points":3086400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `attributes_for_keys` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"63712b203f7a63b38bcb9eea53c3001d","location":{"path":"lib/api/helpers.rb","lines":{"begin":289,"end":299}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `project_finder_params` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"268f87eb93bd855d1807610fcddd6efe","location":{"path":"lib/api/helpers.rb","lines":{"begin":408,"end":420}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `present_carrierwave_file!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"360f85bdb8a06a280a62224143731f15","location":{"path":"lib/api/helpers.rb","lines":{"begin":440,"end":452}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `sudo!` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ddabeb34a3b3e3067c6b88323ce10dcc","location":{"path":"lib/api/helpers.rb","lines":{"begin":468,"end":487}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `users.rb` has 652 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"da0cb2b4e7ad1056fa1e4f7a0fc774d7","location":{"path":"lib/api/users.rb","lines":{"begin":1,"end":837}},"other_locations":[],"remediation_points":6988800,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_groups` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"eea17dfbc1a8fa0837c83d51e2708400","location":{"path":"lib/api/groups.rb","lines":{"begin":42,"end":56}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `inject_rblineprof` has a Cognitive Complexity of 49 (exceeds 5 allowed). Consider refactoring.","fingerprint":"16157b2c336822ada397c30b37d0e63b","location":{"path":"lib/peek/rblineprof/custom_controller_helpers.rb","lines":{"begin":17,"end":100}},"other_locations":[],"remediation_points":4550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `inject_rblineprof` has 62 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"9665938c091b2575d937990fbc609dac","location":{"path":"lib/peek/rblineprof/custom_controller_helpers.rb","lines":{"begin":17,"end":100}},"other_locations":[],"remediation_points":1488000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `flatten_comments` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3acab7e81bc6f21925b3944dc13c9d25","location":{"path":"lib/bitbucket_server/representation/comment.rb","lines":{"begin":87,"end":111}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check_errors!` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5656af648a8148a4747d9750b93c8a0c","location":{"path":"lib/bitbucket_server/connection.rb","lines":{"begin":62,"end":74}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `extract_ref` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8b8bde6183c235753993662c791c5f50","location":{"path":"lib/extracts_path.rb","lines":{"begin":40,"end":74}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `assign_ref_vars` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"994b000ce7697f00c9d2fdef6d5f219c","location":{"path":"lib/extracts_path.rb","lines":{"begin":108,"end":133}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `gitaly_configuration_toml` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7ce1b00e1e01659ed25ab9357f9d47d7","location":{"path":"lib/gitlab/setup_helper.rb","lines":{"begin":14,"end":43}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_with_user_password` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0212b6bdb5ef9f95aaaad8df3d25a15a","location":{"path":"lib/gitlab/auth.rb","lines":{"begin":47,"end":83}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `rate_limit!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"18c084f7a7640dd9934777afe795d6e1","location":{"path":"lib/gitlab/auth.rb","lines":{"begin":85,"end":104}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `service_request_check` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fcdcb167117e9fbdc60bcccfa8c52ce9","location":{"path":"lib/gitlab/auth.rb","lines":{"begin":112,"end":128}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `deploy_token_check` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3349988ec83e4933b92c3a7e3824cc58","location":{"path":"lib/gitlab/auth.rb","lines":{"begin":185,"end":199}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `lfs_token_check` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4f1a1535569de597558d429c01457d5e","location":{"path":"lib/gitlab/auth.rb","lines":{"begin":202,"end":228}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `build_access_token_check` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1e8fcdca34d7247788de08e6051143d2","location":{"path":"lib/gitlab/auth.rb","lines":{"begin":230,"end":245}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `setup_subscribers` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"85ca5972d830d83ef927532dcf66122f","location":{"path":"lib/gitlab/performance_bar/peek_query_tracker.rb","lines":{"begin":17,"end":36}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `fields` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"dbfd2a1c630915fc2b4befaca081c456","location":{"path":"lib/gitlab/slash_commands/presenters/issue_base.rb","lines":{"begin":21,"end":39}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `format_response` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"57bd6ed972d21a41e82e376f51d7a76e","location":{"path":"lib/gitlab/slash_commands/presenters/base.rb","lines":{"begin":47,"end":58}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `text` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0fea0819cd1d628d704dab5f9f56ae5c","location":{"path":"lib/gitlab/slash_commands/presenters/issue_show.rb","lines":{"begin":40,"end":53}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `file.rb` has 255 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"bcd7afc2511311acf93e48f89b257ee8","location":{"path":"lib/gitlab/diff/file.rb","lines":{"begin":1,"end":356}},"other_locations":[],"remediation_points":1272000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `simple_viewer_class` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.","fingerprint":"455a703cf96823be99c475db08de0c9f","location":{"path":"lib/gitlab/diff/file.rb","lines":{"begin":311,"end":339}},"other_locations":[],"remediation_points":1350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `viewer_class_from` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cae1cebb92d35a5859ef229108d1e4c1","location":{"path":"lib/gitlab/diff/file.rb","lines":{"begin":345,"end":353}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `File` has 54 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"4872155d7a8ab149e4dbfdd0a34d314f","location":{"path":"lib/gitlab/diff/file.rb","lines":{"begin":3,"end":354}},"other_locations":[],"remediation_points":4600000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `simple_viewer_class` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"c663249a7eb69d79d53815c02da1287e","location":{"path":"lib/gitlab/diff/file.rb","lines":{"begin":311,"end":339}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `initialize` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"62d56326c2f46a050e8a9f1219488e25","location":{"path":"lib/gitlab/diff/line.rb","lines":{"begin":10,"end":10}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Position` has 22 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"44aef8e9a5055d83b7605ef01a8ffcd5","location":{"path":"lib/gitlab/diff/position.rb","lines":{"begin":5,"end":150}},"other_locations":[],"remediation_points":1400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `highlight` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"922e6aee35a311a79f1043b07ee7064d","location":{"path":"lib/gitlab/diff/highlight.rb","lines":{"begin":21,"end":44}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `highlight_line` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b41f2627f4109bdc5b74287cf3ba0d21","location":{"path":"lib/gitlab/diff/highlight.rb","lines":{"begin":48,"end":64}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `parallelize` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.","fingerprint":"631e96ad1ef01b07245ee2a542d76275","location":{"path":"lib/gitlab/diff/parallel_diff.rb","lines":{"begin":10,"end":58}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `parallelize` has 35 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"c801b9d4ce1a0b15a92b13570e4b4a73","location":{"path":"lib/gitlab/diff/parallel_diff.rb","lines":{"begin":10,"end":58}},"other_locations":[],"remediation_points":840000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `diff_stats_collection` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d1edfc550eecbd5daa471adab0ab3385","location":{"path":"lib/gitlab/diff/file_collection/base.rb","lines":{"begin":50,"end":59}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `parse` has a Cognitive Complexity of 29 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bfc88a6b4fdafecdb4881ebd0a709e90","location":{"path":"lib/gitlab/diff/parser.rb","lines":{"begin":6,"end":63}},"other_locations":[],"remediation_points":2550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `parse` has 42 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"6d81f9668924e2328ae1d3b978012b2e","location":{"path":"lib/gitlab/diff/parser.rb","lines":{"begin":6,"end":63}},"other_locations":[],"remediation_points":1008000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `trace` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"678e46609976cce578070c879cdeaa09","location":{"path":"lib/gitlab/diff/position_tracer.rb","lines":{"begin":18,"end":86}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `trace_added_line` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3d3253565d5f52fdc8e227a610a4ba02","location":{"path":"lib/gitlab/diff/position_tracer.rb","lines":{"begin":90,"end":127}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `trace_removed_line` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7a2345cd4720ceb59ce0efdf2c931d70","location":{"path":"lib/gitlab/diff/position_tracer.rb","lines":{"begin":129,"end":158}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `trace_unchanged_line` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"68d033636c672cf93801ba4a440a9b10","location":{"path":"lib/gitlab/diff/position_tracer.rb","lines":{"begin":160,"end":199}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `map_line_number` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"dc329e8839fe1c0b24ddd3dbaa5b8485","location":{"path":"lib/gitlab/diff/line_mapper.rb","lines":{"begin":30,"end":61}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `popen_with_detail` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1f3016518aa9a963248a02cac889fd75","location":{"path":"lib/gitlab/popen.rb","lines":{"begin":18,"end":51}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `request_cache` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c7f577fa9ce2f42e5f27aac8db514fe3","location":{"path":"lib/gitlab/cache/request_cache.rb","lines":{"begin":23,"end":58}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `store_in_cache_if_needed` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"baa77c71d04626f49bd5c1e57a0e2ca9","location":{"path":"lib/gitlab/cache/ci/project_pipeline_status.rb","lines":{"begin":92,"end":100}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `aggregate_rblineprof` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5c969b511dec60cc5ae82a493581e987","location":{"path":"lib/gitlab/sherlock/line_profiler.rb","lines":{"begin":59,"end":85}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `subscribe_to_active_record` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"cb47c9a23a0a444f0c02f2ce82101c23","location":{"path":"lib/gitlab/sherlock/transaction.rb","lines":{"begin":88,"end":96}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `remove_keys_not_found_in_db` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"110e16cf8bc4b13203ae16707be71f9e","location":{"path":"lib/gitlab/shell.rb","lines":{"begin":229,"end":246}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Shell` has 37 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"9bb914cee67608b26add7bcc4f81b00b","location":{"path":"lib/gitlab/shell.rb","lines":{"begin":6,"end":445}},"other_locations":[],"remediation_points":2900000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `bulk_insert` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"46738c8e24da7d644d4613d9605d7262","location":{"path":"lib/gitlab/database.rb","lines":{"begin":167,"end":197}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `each` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f71d3611436ba9323475933db3b224e0","location":{"path":"lib/gitlab/gitaly_client/conflict_files_stitcher.rb","lines":{"begin":10,"end":26}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `each` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a4899ef1be9285a53b4a3412a311ee1f","location":{"path":"lib/gitlab/gitaly_client/blobs_stitcher.rb","lines":{"begin":10,"end":29}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `update_page` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"5e1fe756db8adf0384ead3d7ca4d40bb","location":{"path":"lib/gitlab/gitaly_client/wiki_service.rb","lines":{"begin":41,"end":41}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_file` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f2ac95cd9282cf75ce51181ba7bcf087","location":{"path":"lib/gitlab/gitaly_client/wiki_service.rb","lines":{"begin":128,"end":152}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `wiki_page_from_iterator` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8d18749d8e89aa853f84d5b8ab2fa1e0","location":{"path":"lib/gitlab/gitaly_client/wiki_service.rb","lines":{"begin":171,"end":192}},"other_locations":[],"remediation_points":1350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `commit_service.rb` has 334 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"459baf31c3d4b57e8c9df845211d85c9","location":{"path":"lib/gitlab/gitaly_client/commit_service.rb","lines":{"begin":1,"end":431}},"other_locations":[],"remediation_points":2409600,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `diff` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6776fdb83e8edfae5378441fd52a4925","location":{"path":"lib/gitlab/gitaly_client/commit_service.rb","lines":{"begin":33,"end":63}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `tree_entry` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"00baf604adba61e863f7d154a8dee72b","location":{"path":"lib/gitlab/gitaly_client/commit_service.rb","lines":{"begin":78,"end":109}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_commit` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"186f0eba8b7425edc8cc3c73c38cd40a","location":{"path":"lib/gitlab/gitaly_client/commit_service.rb","lines":{"begin":242,"end":262}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `CommitService` has 31 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"49c846fd451fa9152c64a84b18cea747","location":{"path":"lib/gitlab/gitaly_client/commit_service.rb","lines":{"begin":3,"end":429}},"other_locations":[],"remediation_points":2300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `ref_service.rb` has 252 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"eb1efcba78462f496979baf4cb6cd2ad","location":{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":1,"end":327}},"other_locations":[],"remediation_points":1228800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `RefService` has 31 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"00c5cfab9191c2db4747039325616652","location":{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":3,"end":325}},"other_locations":[],"remediation_points":2300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `repository_service.rb` has 323 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"493989beb71c8d830c2b79d5553c6147","location":{"path":"lib/gitlab/gitaly_client/repository_service.rb","lines":{"begin":1,"end":396}},"other_locations":[],"remediation_points":2251200,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `fetch_remote` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f3aee0aba331f59ab18ffef3792b052a","location":{"path":"lib/gitlab/gitaly_client/repository_service.rb","lines":{"begin":64,"end":81}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `RepositoryService` has 35 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"6ca9b3950e1ce99211058bd24b696cd9","location":{"path":"lib/gitlab/gitaly_client/repository_service.rb","lines":{"begin":3,"end":394}},"other_locations":[],"remediation_points":2700000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `user_squash` has 7 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"3244c060b29a67dfaca14f5fd90c0c69","location":{"path":"lib/gitlab/gitaly_client/operation_service.rb","lines":{"begin":207,"end":207}},"other_locations":[],"remediation_points":525000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `user_commit_files` has 8 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"e11b9bf00977dfd40d466849a02aad7e","location":{"path":"lib/gitlab/gitaly_client/operation_service.rb","lines":{"begin":234,"end":235}},"other_locations":[],"remediation_points":600000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `user_commit_files_request_header` has 8 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"74f8cedbd2e30f093d96c68640b17e2f","location":{"path":"lib/gitlab/gitaly_client/operation_service.rb","lines":{"begin":316,"end":317}},"other_locations":[],"remediation_points":600000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `operation_service.rb` has 284 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"e9b10e91d8aa4aecb2dd40e2ff73151e","location":{"path":"lib/gitlab/gitaly_client/operation_service.rb","lines":{"begin":1,"end":343}},"other_locations":[],"remediation_points":1689600,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `user_create_branch` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f83bdf53147b9d2930979ca6a622f44f","location":{"path":"lib/gitlab/gitaly_client/operation_service.rb","lines":{"begin":48,"end":69}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `user_merge_branch` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"96dce6cec3c6d464fe3b164bf05adf61","location":{"path":"lib/gitlab/gitaly_client/operation_service.rb","lines":{"begin":101,"end":137}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `user_commit_files` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"b324bd9e25781ee4f45ebe63530654b5","location":{"path":"lib/gitlab/gitaly_client/operation_service.rb","lines":{"begin":233,"end":274}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `define_predicate_methods` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1748406e1b1a3efed8bbdbc2f7310efb","location":{"path":"lib/gitlab/fake_application_settings.rb","lines":{"begin":9,"end":19}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `read` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"306bfd1a5668c64b46f9a50ffcb84761","location":{"path":"lib/gitlab/http_io.rb","lines":{"begin":76,"end":100}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `get_chunk` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"718a1c6197d9172beada51e8fb725aa8","location":{"path":"lib/gitlab/http_io.rb","lines":{"begin":147,"end":173}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `HttpIO` has 21 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"f90f3bbe0002c0551d2c102e8d6cc2bb","location":{"path":"lib/gitlab/http_io.rb","lines":{"begin":5,"end":192}},"other_locations":[],"remediation_points":1300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `system_usage_data` has 49 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e44de166e4765b13674da7e6f1adbb03","location":{"path":"lib/gitlab/usage_data.rb","lines":{"begin":35,"end":85}},"other_locations":[],"remediation_points":1176000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `print_methods` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"74e543934a1cb0119b1cb9bc2bef6f4a","location":{"path":"lib/gitlab/profiler/total_time_flat_printer.rb","lines":{"begin":13,"end":36}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `format_issue_comment_body` has 6 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"4b9885935625732368d27f4d57c09a6b","location":{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":327,"end":327}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `importer.rb` has 283 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"1456110a9f0cea86dbda2fdc6ff8b006","location":{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":1,"end":371}},"other_locations":[],"remediation_points":1675200,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_issues` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fda606a33fed0d051037b3e7ec312764","location":{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":82,"end":126}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_issue_comments` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e40d64a7ad3fca74d9f0603fcdfde073","location":{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":146,"end":179}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `format_updates` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.","fingerprint":"627781504f289b64edb3cae5ded46686","location":{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":237,"end":293}},"other_locations":[],"remediation_points":1550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `format_attachments` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3eae70f0804e77646cf183b658337852","location":{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":312,"end":325}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `import_issues` has 33 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"140622e56443443be6d89cf7f8134759","location":{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":82,"end":126}},"other_locations":[],"remediation_points":792000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `import_issue_comments` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"81d77c1a0b5ebc4248e39438a9b784c1","location":{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":146,"end":179}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `format_updates` has 43 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"9b843b25138348d8d9e7cf6df7a2f0ed","location":{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":237,"end":293}},"other_locations":[],"remediation_points":1032000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `full_path` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"200d2aea21e0c3f34efb9b3a8051a82f","location":{"path":"lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb","lines":{"begin":7,"end":15}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `rank_rows` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"dc13ef2bef6a33cc8bc4e67b71564878","location":{"path":"lib/gitlab/database/median.rb","lines":{"begin":140,"end":174}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `migration_helpers.rb` has 543 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"01e21ae134d30a14bd512652b7a940ab","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":1,"end":1078}},"other_locations":[],"remediation_points":5419200,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `add_timestamps_with_timezone` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c59bbcc4bf6935d5c169f128844c4c91","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":17,"end":40}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `add_concurrent_foreign_key` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ce151ede627cdfdf31c565ade50905ea","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":152,"end":206}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `disable_statement_timeout` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"31b96e31df6242d18aba1d4abf07f1f9","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":240,"end":272}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update_column_in_batches` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.","fingerprint":"21d13cfd466605197c31d8958201310c","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":323,"end":380}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `add_column_with_default` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"009915036d7d7932903bc6f766511a5e","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":406,"end":438}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `copy_indexes` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.","fingerprint":"60d019b8234aa4a0d7d445a95ee1e8ae","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":817,"end":859}},"other_locations":[],"remediation_points":1350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `bulk_queue_background_migration_jobs_by_range` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"128bc9f2059885db7d254bdb6a31d4b3","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":972,"end":993}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `add_concurrent_foreign_key` has 32 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"f6243b1ac45a70775392f6137b36e35f","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":152,"end":206}},"other_locations":[],"remediation_points":768000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `update_column_in_batches` has 37 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"6dc99342ecbf79855eb8044b9e00cbdd","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":323,"end":380}},"other_locations":[],"remediation_points":888000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `change_column_type_using_background_migration` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"39133c1e5a2502598dbf0ec8dc0af798","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":593,"end":640}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `rename_column_using_background_migration` has 32 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"fc89feb63b650bd5547ac2ba44210012","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":670,"end":731}},"other_locations":[],"remediation_points":768000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `copy_indexes` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"8b98ee08501009b6bf421b8064689b30","location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":817,"end":859}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `fetch_config` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4b3b0f6bd727a62eae22091574b81015","location":{"path":"lib/gitlab/mail_room.rb","lines":{"begin":31,"end":49}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `note_url` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"59ca4f1b16f978d96a1c7e36f7e69d62","location":{"path":"lib/gitlab/url_builder.rb","lines":{"begin":52,"end":71}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `rewrite` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f45f6ba5c50a0ab576b49cf1b282a615","location":{"path":"lib/gitlab/gfm/uploads_rewriter.rb","lines":{"begin":19,"end":29}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `unfold_reference` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"be381321436537b6ec6941dcff14d0b9","location":{"path":"lib/gitlab/gfm/reference_rewriter.rb","lines":{"begin":56,"end":72}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `sections` has a Cognitive Complexity of 24 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0f6b50ef494458ae4f54e52a28568869","location":{"path":"lib/gitlab/conflict/file.rb","lines":{"begin":49,"end":110}},"other_locations":[],"remediation_points":2050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_match_line_header` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4fef2b9774e94d0bd02f1158378f701c","location":{"path":"lib/gitlab/conflict/file.rb","lines":{"begin":123,"end":137}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `as_json` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ed7b03c5a2c2d110a9a9db9e00f3d8d0","location":{"path":"lib/gitlab/conflict/file.rb","lines":{"begin":150,"end":168}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `sections` has 36 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"629b80d357b767eb9b1f37185affacd4","location":{"path":"lib/gitlab/conflict/file.rb","lines":{"begin":49,"end":110}},"other_locations":[],"remediation_points":864000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `which` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"565d3b45775f71100b9c7277076be0f6","location":{"path":"lib/gitlab/utils.rb","lines":{"begin":63,"end":74}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `render_go_doc` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e0fac0dd89237b115b26def1d32171a6","location":{"path":"lib/gitlab/middleware/go.rb","lines":{"begin":22,"end":33}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `with_open_files` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"83c9d42c4a44fe57eeee2a2b269ddb60","location":{"path":"lib/gitlab/middleware/multipart.rb","lines":{"begin":38,"end":57}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `decorate_params_value` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"32d0634549f8a039069852514c0c4e20","location":{"path":"lib/gitlab/middleware/multipart.rb","lines":{"begin":60,"end":82}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `initialize` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"100b77808e789566083b5722f853d491","location":{"path":"lib/gitlab/legacy_github_import/project_creator.rb","lines":{"begin":6,"end":6}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_issues` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3bfd1976bd1c3f5b63ad73183773103c","location":{"path":"lib/gitlab/legacy_github_import/importer.rb","lines":{"begin":117,"end":136}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_pull_requests` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d3b76e6c4972600af102835c1739459a","location":{"path":"lib/gitlab/legacy_github_import/importer.rb","lines":{"begin":139,"end":165}},"other_locations":[],"remediation_points":1550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create_comments` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8131d9fa54052fb8688da41fca5b29ff","location":{"path":"lib/gitlab/legacy_github_import/importer.rb","lines":{"begin":222,"end":245}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_wiki` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c9017489731af795ba61fdc442e91922","location":{"path":"lib/gitlab/legacy_github_import/importer.rb","lines":{"begin":266,"end":278}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_releases` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6cf70c97f808766cb57657e58420cb89","location":{"path":"lib/gitlab/legacy_github_import/importer.rb","lines":{"begin":280,"end":291}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Importer` has 27 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"564493c4d63110500702ce8552822c02","location":{"path":"lib/gitlab/legacy_github_import/importer.rb","lines":{"begin":3,"end":339}},"other_locations":[],"remediation_points":1900000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `each_object_to_import` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1c46822af079d421dee8173b07f3b80a","location":{"path":"lib/gitlab/github_import/parallel_scheduling.rb","lines":{"begin":78,"end":106}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `each_page` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d8e73017cc50cf5ae0ff94ea436d3ced","location":{"path":"lib/gitlab/github_import/client.rb","lines":{"begin":91,"end":112}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `with_rate_limit` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f8e67dd27500a710187cc201ddc1dded","location":{"path":"lib/gitlab/github_import/client.rb","lines":{"begin":133,"end":149}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Client` has 22 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"8c409ff9cd79515f70c131a687a58c2d","location":{"path":"lib/gitlab/github_import/client.rb","lines":{"begin":16,"end":216}},"other_locations":[],"remediation_points":1400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create_sub_relations` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"de78150728d4deb7bbd23ae848875928","location":{"path":"lib/gitlab/import_export/project_tree_restorer.rb","lines":{"begin":134,"end":159}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `process_sub_relation` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"357889b554818506acea0dfc8f000afc","location":{"path":"lib/gitlab/import_export/project_tree_restorer.rb","lines":{"begin":161,"end":171}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e960e3648215aee14c4e5443b5f897b0","location":{"path":"lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb","lines":{"begin":27,"end":49}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `build!` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1dd9f4457d861e5b12020c99ef408fe4","location":{"path":"lib/gitlab/import_export/after_export_strategy_builder.rb","lines":{"begin":6,"end":17}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `parse!` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"343742e2d2dc60bd360734eccf276cc8","location":{"path":"lib/gitlab/import_export/merge_request_parser.rb","lines":{"begin":13,"end":22}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `copy_project_uploads` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d141822a7e49c9987620bcbcaf7749c0","location":{"path":"lib/gitlab/import_export/uploads_manager.rb","lines":{"begin":44,"end":56}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Importer` has 21 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"9b57d9528846dda6448ed9d3a49747c3","location":{"path":"lib/gitlab/import_export/importer.rb","lines":{"begin":3,"end":129}},"other_locations":[],"remediation_points":1300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `imported_object` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7150a66352dfbd84b82e4bfa60942b90","location":{"path":"lib/gitlab/import_export/relation_factory.rb","lines":{"begin":198,"end":209}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `RelationFactory` has 30 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"4f17a460cbd29b77fbb38f05dd3866a4","location":{"path":"lib/gitlab/import_export/relation_factory.rb","lines":{"begin":3,"end":281}},"other_locations":[],"remediation_points":2200000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `project_attributes` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"80346e71805b8ddd466c6b77a8f2e1e6","location":{"path":"lib/gitlab/import_export/group_project_object_builder.rb","lines":{"begin":49,"end":62}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `build` has 7 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"34c8251c050af78b0e432e62b3bf6eaf","location":{"path":"lib/gitlab/data_builder/push.rb","lines":{"begin":56,"end":56}},"other_locations":[],"remediation_points":525000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `build` has 29 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e5d84ee4338c67ad951b6130bf67d21a","location":{"path":"lib/gitlab/data_builder/push.rb","lines":{"begin":56,"end":98}},"other_locations":[],"remediation_points":696000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `build` has 49 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"ac9809b4ea4dd090261cab4784070118","location":{"path":"lib/gitlab/data_builder/build.rb","lines":{"begin":6,"end":68}},"other_locations":[],"remediation_points":1176000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `absolute_image_urls` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b291bb28522abd9dde68b0387000c263","location":{"path":"lib/gitlab/hook_data/base_builder.rb","lines":{"begin":22,"end":35}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `safe_hook_attributes` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"682e99b5b83244042ccc251929255272","location":{"path":"lib/gitlab/hook_data/merge_request_builder.rb","lines":{"begin":4,"end":32}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `gitaly_client.rb` has 321 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"aa680293a25a35a2aacd0db231b38fc3","location":{"path":"lib/gitlab/gitaly_client.rb","lines":{"begin":1,"end":484}},"other_locations":[],"remediation_points":2222400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `call` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"96a0315d42972363e3be0faaf33fcff8","location":{"path":"lib/gitlab/gitaly_client.rb","lines":{"begin":124,"end":149}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `request_kwargs` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"62070bc24b2fd5133e0a7beab00d9b77","location":{"path":"lib/gitlab/gitaly_client.rb","lines":{"begin":197,"end":221}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `feature_enabled?` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"731633ec642f522eb73bf54d30cc752d","location":{"path":"lib/gitlab/gitaly_client.rb","lines":{"begin":239,"end":269}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `enforce_gitaly_request_limits` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a75016ab6541606469d82939c599b4ad","location":{"path":"lib/gitlab/gitaly_client.rb","lines":{"begin":317,"end":340}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `issues` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"625fb557175f1173f5275a5c774ad3d6","location":{"path":"lib/gitlab/reference_extractor.rb","lines":{"begin":34,"end":45}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `metrics` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"77acda463966e33d89aa2e8120528ca8","location":{"path":"lib/gitlab/health_checks/simple_abstract_check.rb","lines":{"begin":17,"end":25}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `profile` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fee59aefc8f7079890f207651b7981f0","location":{"path":"lib/gitlab/profiler.rb","lines":{"begin":38,"end":79}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `debug` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"fdc82615456346012b61b614b718e980","location":{"path":"lib/gitlab/profiler.rb","lines":{"begin":91,"end":109}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `protected_branch_push_checks` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"32eb78f0c97e724ad8a8d5553b542d3f","location":{"path":"lib/gitlab/checks/change_access.rb","lines":{"begin":89,"end":99}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `protected_tag_checks` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"06f719f8ef5751a719db79c5e8c24be7","location":{"path":"lib/gitlab/checks/change_access.rb","lines":{"begin":111,"end":120}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `ChangeAccess` has 25 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"c155eb2e64ad1ed777bca59ce76dbaef","location":{"path":"lib/gitlab/checks/change_access.rb","lines":{"begin":3,"end":214}},"other_locations":[],"remediation_points":1700000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8ee7f543cac7d3fb2d03c2cf82f45f09","location":{"path":"lib/gitlab/checks/commit_check.rb","lines":{"begin":16,"end":28}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `stop` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0578d528100ce788878962a15cbeec2a","location":{"path":"lib/gitlab/daemon.rb","lines":{"begin":39,"end":51}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `verification_status` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"278e4598e8040e0a02ba62435277a8b2","location":{"path":"lib/gitlab/gpg/commit.rb","lines":{"begin":100,"end":112}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `initialize` has 7 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"26ed72f706cbd880946abd65775661b1","location":{"path":"lib/gitlab/bitbucket_server_import/project_creator.rb","lines":{"begin":6,"end":6}},"other_locations":[],"remediation_points":525000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `importer.rb` has 275 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"144e11b64a9050685cd582324f4c5650","location":{"path":"lib/gitlab/bitbucket_server_import/importer.rb","lines":{"begin":3,"end":388}},"other_locations":[],"remediation_points":1560000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Importer` has 28 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"fe99b8de0e4e755d01efc8644f28aa32","location":{"path":"lib/gitlab/bitbucket_server_import/importer.rb","lines":{"begin":5,"end":386}},"other_locations":[],"remediation_points":2000000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ensure_hash` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"32c1c4d58157c12dafbd9c492c51a5aa","location":{"path":"lib/gitlab/graphql/variables.rb","lines":{"begin":17,"end":34}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `instrument` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d95b586a43aa6e99b3f767263ce65d16","location":{"path":"lib/gitlab/graphql/present/instrumentation.rb","lines":{"begin":5,"end":32}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `instrument` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7b1d8edac9e190461296777cc29e22e5","location":{"path":"lib/gitlab/graphql/authorize/instrumentation.rb","lines":{"begin":10,"end":31}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `instrument_methods` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d1438aadd557c25b108eea0106855664","location":{"path":"lib/gitlab/metrics/instrumentation.rb","lines":{"begin":70,"end":81}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `instrument_instance_methods` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"eb2114fa42977c795cf347169a0f542c","location":{"path":"lib/gitlab/metrics/instrumentation.rb","lines":{"begin":88,"end":99}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `instrument` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bca595f2e9128a833295cec69e8c1e4e","location":{"path":"lib/gitlab/metrics/instrumentation.rb","lines":{"begin":118,"end":167}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `instrument` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"46dbb1e9f69c06f7d078548fc6061570","location":{"path":"lib/gitlab/metrics/instrumentation.rb","lines":{"begin":118,"end":167}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `submit` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b4ef0ec3fde55c4188a9f170e0f50760","location":{"path":"lib/gitlab/metrics/transaction.rb","lines":{"begin":114,"end":129}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `submit_metrics` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9b54961b8f59db3e5122c4208ded0a04","location":{"path":"lib/gitlab/metrics/influx_db.rb","lines":{"begin":48,"end":62}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `pool` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"85d4413ec95715375e2c5ed3923bf252","location":{"path":"lib/gitlab/metrics/influx_db.rb","lines":{"begin":165,"end":181}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `measure` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"9ce1cb49fa0e90749c14924f01742eab","location":{"path":"lib/gitlab/metrics/influx_db.rb","lines":{"begin":95,"end":133}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `initialize` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"04b6cf203bf783b43e1ac1b54b888441","location":{"path":"lib/gitlab/fogbugz_import/project_creator.rb","lines":{"begin":6,"end":6}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `format_issue_comment_body` has 6 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"1ec61e94902238cc9befa5a1daf367e9","location":{"path":"lib/gitlab/fogbugz_import/importer.rb","lines":{"begin":279,"end":279}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_cases` has a Cognitive Complexity of 16 (exceeds 5 allowed). Consider refactoring.","fingerprint":"deff1dce2bf92e8e8a73a7dad5c4c5c9","location":{"path":"lib/gitlab/fogbugz_import/importer.rb","lines":{"begin":102,"end":146}},"other_locations":[],"remediation_points":1250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_issue_comments` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3399ad65a3497aa307eaeb32cb5e83e6","location":{"path":"lib/gitlab/fogbugz_import/importer.rb","lines":{"begin":158,"end":196}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `import_cases` has 34 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"cdbe17ce063af3997de1ea4bb7b62f8b","location":{"path":"lib/gitlab/fogbugz_import/importer.rb","lines":{"begin":102,"end":146}},"other_locations":[],"remediation_points":816000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `import_issue_comments` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"a350dd17c12b659d965d2849ad05d077","location":{"path":"lib/gitlab/fogbugz_import/importer.rb","lines":{"begin":158,"end":196}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `git_access.rb` has 288 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"217b36994b7bc085e2426b68d5ae12da","location":{"path":"lib/gitlab/git_access.rb","lines":{"begin":3,"end":372}},"other_locations":[],"remediation_points":1747200,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check_authentication_abilities!` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"12742436ece459400a3eaf9d0a1bb60c","location":{"path":"lib/gitlab/git_access.rb","lines":{"begin":128,"end":139}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ensure_project_on_push!` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"96a9f1789e7e7f1f1ba2da1e54b194c6","location":{"path":"lib/gitlab/git_access.rb","lines":{"begin":189,"end":213}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check_push_access!` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3ea1e3ce0a376bfea8bcf0212ff90809","location":{"path":"lib/gitlab/git_access.rb","lines":{"begin":233,"end":251}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `GitAccess` has 41 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"8b604fd91125836b6b7febd9f9706b53","location":{"path":"lib/gitlab/git_access.rb","lines":{"begin":4,"end":371}},"other_locations":[],"remediation_points":3300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create_service_account` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ede2112edc62f1c1aaab207b4d2d3bb8","location":{"path":"lib/gitlab/kubernetes/helm/api.rb","lines":{"begin":49,"end":59}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `create_cluster_role_binding` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"35fbb979c3d0eb66f9ca187bb5295521","location":{"path":"lib/gitlab/kubernetes/helm/api.rb","lines":{"begin":61,"end":71}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `initialize` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"a6788548613abbede0f945c93f499e01","location":{"path":"lib/gitlab/bitbucket_import/project_creator.rb","lines":{"begin":6,"end":6}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_issues` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"dc7e6d5a30b4b296140a3b3ef8f0e44a","location":{"path":"lib/gitlab/bitbucket_import/importer.rb","lines":{"begin":74,"end":106}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_issue_comments` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a48dbffa8fed1a096624520d3125343e","location":{"path":"lib/gitlab/bitbucket_import/importer.rb","lines":{"begin":109,"end":134}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `import_pull_requests` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5a484ad1bd84dce7a3bef35405340b71","location":{"path":"lib/gitlab/bitbucket_import/importer.rb","lines":{"begin":147,"end":183}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `import_pull_requests` has 31 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"bb107ec4ecd65c38b8c57821b432e1bd","location":{"path":"lib/gitlab/bitbucket_import/importer.rb","lines":{"begin":147,"end":183}},"other_locations":[],"remediation_points":744000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `get_info` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9ccb0c18a6f37f110aadd6045933f910","location":{"path":"lib/gitlab/auth/ldap/auth_hash.rb","lines":{"begin":19,"end":36}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `tls_options` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"f8cf4abbbf7c74ebb10433a1b19d6e25","location":{"path":"lib/gitlab/auth/ldap/config.rb","lines":{"begin":210,"end":227}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Config` has 38 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"d8bf80e499225a5522c5bf02c2b5de50","location":{"path":"lib/gitlab/auth/ldap/config.rb","lines":{"begin":5,"end":248}},"other_locations":[],"remediation_points":3000000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `allowed?` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1b6717c2575d1b9634403637d926a53e","location":{"path":"lib/gitlab/auth/ldap/access.rb","lines":{"begin":17,"end":32}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `allowed?` has a Cognitive Complexity of 20 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1b6717c2575d1b9634403637d926a53e","location":{"path":"lib/gitlab/auth/ldap/access.rb","lines":{"begin":41,"end":63}},"other_locations":[],"remediation_points":1650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `ldap_search` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5d595594b703b1d9783bd955a198807f","location":{"path":"lib/gitlab/auth/ldap/adapter.rb","lines":{"begin":52,"end":83}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `each_pair` has a Cognitive Complexity of 43 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0b175175ba20f21bb15cdf8c88896cd1","location":{"path":"lib/gitlab/auth/ldap/dn.rb","lines":{"begin":58,"end":194}},"other_locations":[],"remediation_points":3950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `initialize_array` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d6d33eb81193471d4408113ca113deac","location":{"path":"lib/gitlab/auth/ldap/dn.rb","lines":{"begin":267,"end":281}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `each_pair` has 131 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"648c4eb09b87d49a1d79f5d0a77de3ea","location":{"path":"lib/gitlab/auth/ldap/dn.rb","lines":{"begin":58,"end":194}},"other_locations":[],"remediation_points":3144000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `login` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c350e7d285ce68091161b324ed9ca293","location":{"path":"lib/gitlab/auth/ldap/authentication.rb","lines":{"begin":11,"end":20}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `save` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5b220e2f520a6fff2f90c05ba11999ba","location":{"path":"lib/gitlab/auth/o_auth/user.rb","lines":{"begin":37,"end":52}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_user` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7a4874f3ff061cb0b1aeef04800ada15","location":{"path":"lib/gitlab/auth/o_auth/user.rb","lines":{"begin":60,"end":69}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `update_profile` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"38ded984fa19d27580db26e0540ac53f","location":{"path":"lib/gitlab/auth/o_auth/user.rb","lines":{"begin":229,"end":253}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `User` has 32 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"e416592e77f689818995fceca5f571bc","location":{"path":"lib/gitlab/auth/o_auth/user.rb","lines":{"begin":9,"end":268}},"other_locations":[],"remediation_points":2400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_user` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ba2b8382483d9be5260822909fe26d56","location":{"path":"lib/gitlab/auth/saml/user.rb","lines":{"begin":16,"end":28}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `cache_method_output` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"173a260b95048ce7cfc5164ebeeaa45c","location":{"path":"lib/gitlab/repository_cache_adapter.rb","lines":{"begin":44,"end":70}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `expire_method_caches` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6938667a593219bc8d59f8046973c533","location":{"path":"lib/gitlab/repository_cache_adapter.rb","lines":{"begin":73,"end":86}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `mark` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7f23977dd6af02c109c5ecaf6ec88e79","location":{"path":"lib/gitlab/string_range_marker.rb","lines":{"begin":16,"end":45}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `position_mapping` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"75e44855c1190acfa386bf060fe0c79c","location":{"path":"lib/gitlab/string_range_marker.rb","lines":{"begin":50,"end":88}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `log_response` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7bd641d0b563cc47dc64d5b378494dd5","location":{"path":"lib/gitlab/storage_check/cli.rb","lines":{"begin":49,"end":68}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_permission!` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"596d8f0ed603043a25830a78af0b7870","location":{"path":"lib/gitlab/email/handler/reply_processing.rb","lines":{"begin":38,"end":47}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6fac16f8fc80d183ffc78d03d2d0245d","location":{"path":"lib/gitlab/email/handler/unsubscribe_handler.rb","lines":{"begin":15,"end":23}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `select_body` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"74ea18f19435d9abee264cd1facce76a","location":{"path":"lib/gitlab/email/reply_parser.rb","lines":{"begin":36,"end":58}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `fix_charset` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bbfbab207f44913ec99bdb0f738ef744","location":{"path":"lib/gitlab/email/reply_parser.rb","lines":{"begin":61,"end":71}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `target_url` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"587a65f54b3a9d4006c9fb8e97881295","location":{"path":"lib/gitlab/email/message/repository_push.rb","lines":{"begin":96,"end":108}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `subject` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"304068a60d47ba6b4d63d0770a3e66e0","location":{"path":"lib/gitlab/email/message/repository_push.rb","lines":{"begin":118,"end":137}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `run!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"06bf786acefc4467f3887ce0b97790b0","location":{"path":"lib/gitlab/cleanup/remote_uploads.rb","lines":{"begin":13,"end":32}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `steal` has a Cognitive Complexity of 16 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bcfc4aae2401e33076da306af1be4a91","location":{"path":"lib/gitlab/background_migration.rb","lines":{"begin":17,"end":38}},"other_locations":[],"remediation_points":1250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate!` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7f4a6a0b5a98750c352c1646b1b3f685","location":{"path":"lib/gitlab/url_blocker.rb","lines":{"begin":8,"end":37}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `redis_store_options` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8e3b7658d5f2da0b6cfe80e375fba17a","location":{"path":"lib/gitlab/redis/wrapper.rb","lines":{"begin":94,"end":116}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `calculate` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"027f65d9b15a5a20ac32b7746317815a","location":{"path":"lib/gitlab/project_authorizations/with_nested_groups.rb","lines":{"begin":16,"end":57}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `allowed?` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5ffe0c3e3d42265eae2251a019292113","location":{"path":"lib/gitlab/user_access.rb","lines":{"begin":30,"end":38}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `can_push_to_branch?` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"303070bb63eb58bcb7a05b3b95932900","location":{"path":"lib/gitlab/user_access.rb","lines":{"begin":64,"end":75}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `sanitize` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ce964ef0b84100c7c6ad7cb863485dc1","location":{"path":"lib/gitlab/ssh_public_key.rb","lines":{"begin":24,"end":38}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8bb3319e6987280746e3bc73708c56f0","location":{"path":"lib/gitlab/bare_repository_import/importer.rb","lines":{"begin":6,"end":27}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `to_kubeconfig` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"b447110ec4376566b0f143ec7955f597","location":{"path":"lib/gitlab/kubernetes.rb","lines":{"begin":85,"end":115}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `extract_filters` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"52f2917ff84bcee853228888bc9d4f20","location":{"path":"lib/gitlab/search/query.rb","lines":{"begin":28,"end":48}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `encode!` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5023e6a2cc1fe373219230a4ada29baa","location":{"path":"lib/gitlab/encoding_helper.rb","lines":{"begin":16,"end":36}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `encode_utf8` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c7dee9a09179a88f06945225a2a221af","location":{"path":"lib/gitlab/encoding_helper.rb","lines":{"begin":51,"end":69}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `handle_exception_response` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"0c36580ef2976c77eabf04d27eda6e0b","location":{"path":"lib/gitlab/prometheus_client.rb","lines":{"begin":78,"end":87}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `generate_full_url` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6347a85ed559890640d3146272847334","location":{"path":"lib/gitlab/url_sanitizer.rb","lines":{"begin":71,"end":78}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `convert` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"427e98fa770dd0ce1ed98ef15ec75fd5","location":{"path":"lib/gitlab/ci/ansi2html.rb","lines":{"begin":136,"end":189}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `open_new_tag` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"3d7e5bd9e7a6c35c9d687fc490695bed","location":{"path":"lib/gitlab/ci/ansi2html.rb","lines":{"begin":232,"end":256}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `get_xterm_color_class` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4936b554b5287229c74930b44325b20c","location":{"path":"lib/gitlab/ci/ansi2html.rb","lines":{"begin":329,"end":341}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Converter` has 69 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"61a0cb2a3f0303ed57f185452426b79a","location":{"path":"lib/gitlab/ci/ansi2html.rb","lines":{"begin":31,"end":346}},"other_locations":[],"remediation_points":6100000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `convert` has 43 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"dd13e7297553d67e7801e8ccc8eed932","location":{"path":"lib/gitlab/ci/ansi2html.rb","lines":{"begin":136,"end":189}},"other_locations":[],"remediation_points":1032000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `matches_pattern?` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ebde1954a4f6fc95ac088dde003d1f7c","location":{"path":"lib/gitlab/ci/build/policy/refs.rb","lines":{"begin":27,"end":38}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `match_entries` has a Cognitive Complexity of 18 (exceeds 5 allowed). Consider refactoring.","fingerprint":"12277d192b4e56c2980eae35423fc226","location":{"path":"lib/gitlab/ci/build/artifacts/metadata.rb","lines":{"begin":54,"end":76}},"other_locations":[],"remediation_points":1450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `read_version` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c8c5a0217b6a28198bd8cb92d0e82b00","location":{"path":"lib/gitlab/ci/build/artifacts/metadata.rb","lines":{"begin":78,"end":92}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Entry` has 21 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"a996c8f128cfc7543769dd21dd5de815","location":{"path":"lib/gitlab/ci/build/artifacts/metadata/entry.rb","lines":{"begin":15,"end":130}},"other_locations":[],"remediation_points":1300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `to_s` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6710c7eee920701d15361f52dbe4bd3d","location":{"path":"lib/gitlab/ci/build/artifacts/path.rb","lines":{"begin":26,"end":36}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `read` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"481c8d08e5e47d4a4d4be68d0d06fe4b","location":{"path":"lib/gitlab/ci/trace/chunked_io.rb","lines":{"begin":69,"end":93}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `chunk_slice_from_offset` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"60dbbad2af42b5e9d17acf1180b8e0d6","location":{"path":"lib/gitlab/ci/trace/chunked_io.rb","lines":{"begin":178,"end":189}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `ChunkedIO` has 26 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"3f6175d5d4d29675f68a7afb44b51fb3","location":{"path":"lib/gitlab/ci/trace/chunked_io.rb","lines":{"begin":7,"end":236}},"other_locations":[],"remediation_points":1800000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `extract_coverage` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a126d9b81c791cb0bd1b8cd455422f3b","location":{"path":"lib/gitlab/ci/trace/stream.rb","lines":{"begin":79,"end":102}},"other_locations":[],"remediation_points":1550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `handle_line` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"6e13932e33f438288cd2d7a23cca7b04","location":{"path":"lib/gitlab/ci/trace/section_parser.rb","lines":{"begin":56,"end":56}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_next_marker` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7b78b80d610593251ebbeb9bb18f6246","location":{"path":"lib/gitlab/ci/trace/section_parser.rb","lines":{"begin":78,"end":93}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_string_or_regexp` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a99d0cd045678d388fd755c238f7757f","location":{"path":"lib/gitlab/ci/config/entry/legacy_validation_helpers.rb","lines":{"begin":43,"end":52}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `to_hash` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bc0685829f332ba16f511159006c4cf9","location":{"path":"lib/gitlab/ci/config/entry/job.rb","lines":{"begin":136,"end":155}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `variables_expressions_syntax` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"736e4fd1de9745ba1bb433fcb5443516","location":{"path":"lib/gitlab/ci/config/entry/policy.rb","lines":{"begin":41,"end":53}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `attributes` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"69c16e006586bdd7ac4cb17f7ede9848","location":{"path":"lib/gitlab/ci/config/entry/attributable.rb","lines":{"begin":9,"end":21}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `helpers` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2d39bb63c3e71381a6cfd8d51654bee4","location":{"path":"lib/gitlab/ci/config/entry/configurable.rb","lines":{"begin":63,"end":75}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `extend!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4fa7fe12ebb0fe9c7eed89e17ece5daf","location":{"path":"lib/gitlab/ci/config/extendable/entry.rb","lines":{"begin":48,"end":72}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `write` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4254fbcc9b1008e1fd6a644e17f14b44","location":{"path":"lib/gitlab/ci/trace.rb","lines":{"begin":83,"end":101}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `unsafe_archive!` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"b90c223da9c451bd90087645954fe5ee","location":{"path":"lib/gitlab/ci/trace.rb","lines":{"begin":125,"end":145}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Trace` has 26 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"d9e217fe03ddebc9f270b441d4194d76","location":{"path":"lib/gitlab/ci/trace.rb","lines":{"begin":3,"end":237}},"other_locations":[],"remediation_points":1800000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform!` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"56ff592ebb6677c3a386b683db2b6821","location":{"path":"lib/gitlab/ci/pipeline/chain/validate/config.rb","lines":{"begin":9,"end":21}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `tokenize` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"60a6e3ebf3ba798ac11eca1daba671b8","location":{"path":"lib/gitlab/ci/pipeline/expression/lexer.rb","lines":{"begin":36,"end":56}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `tree` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"494fb3f15182110d4c2051f9a07b9ddc","location":{"path":"lib/gitlab/ci/pipeline/expression/parser.rb","lines":{"begin":16,"end":31}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `all_cases` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4f41f387d6f9c748719d97b454bbe92f","location":{"path":"lib/gitlab/ci/parsers/junit.rb","lines":{"begin":22,"end":37}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_job_dependencies!` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"97a1046ed696c51fb2ce1bf34f0b7293","location":{"path":"lib/gitlab/ci/yaml_processor.rb","lines":{"begin":139,"end":151}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_on_stop_job!` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"06fcc3fed9dc213e5669186830670f59","location":{"path":"lib/gitlab/ci/yaml_processor.rb","lines":{"begin":161,"end":180}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"5f09e73f4a1aa8458ce984db4f5fce37","location":{"path":"lib/gitlab/downtime_check.rb","lines":{"begin":16,"end":40}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `parse_entry` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4f46e2835f32393dfdf07c66c0f704d0","location":{"path":"lib/gitlab/route_map.rb","lines":{"begin":30,"end":52}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `execute` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e2058f84cad6a346389ce944d7c1c76e","location":{"path":"lib/gitlab/upgrader.rb","lines":{"begin":3,"end":25}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `fuzzy_arel_match` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"44164fac144fbfeea45df1508dfe02db","location":{"path":"lib/gitlab/sql/pattern.rb","lines":{"begin":32,"end":51}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `lazy_page_iterator` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"6ef599b3dc4f1b639d3197c028e40b71","location":{"path":"lib/gitlab/gitlab_import/client.rb","lines":{"begin":59,"end":76}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `merge_hash_tree` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"e6ac0be8cece82ccc2e82ff8dd59e20e","location":{"path":"lib/gitlab/utils/merge_hash.rb","lines":{"begin":58,"end":97}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `listing` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7b99984bc28e1e4cbc4903a72d7ac663","location":{"path":"lib/gitlab/hashed_storage/rake_helper.rb","lines":{"begin":72,"end":87}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `ee_compat_check.rb` has 312 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"2103307f841e293ce32cd4161da95965","location":{"path":"lib/gitlab/ee_compat_check.rb","lines":{"begin":2,"end":440}},"other_locations":[],"remediation_points":2092800,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `generate_patch` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d35820a437b86e532f50e32d127b5ab0","location":{"path":"lib/gitlab/ee_compat_check.rb","lines":{"begin":115,"end":130}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `check_patch` has a Cognitive Complexity of 16 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2b66236ad0f63db5144a0820b94228b9","location":{"path":"lib/gitlab/ee_compat_check.rb","lines":{"begin":178,"end":211}},"other_locations":[],"remediation_points":1250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `EeCompatCheck` has 33 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"36de6fe216cdb6efd396cb08cb43cee5","location":{"path":"lib/gitlab/ee_compat_check.rb","lines":{"begin":4,"end":439}},"other_locations":[],"remediation_points":2500000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg` has 45 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e819c9434d1b44e764eb37a75eb1f24c","location":{"path":"lib/gitlab/ee_compat_check.rb","lines":{"begin":336,"end":412}},"other_locations":[],"remediation_points":1080000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `scrub` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7d4324ffa269ec245847b9dd62388202","location":{"path":"lib/gitlab/sanitizers/svg.rb","lines":{"begin":12,"end":33}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `<=>` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"07c5403aa1fb1c5999cd6138fc8be4c8","location":{"path":"lib/gitlab/version_info.rb","lines":{"begin":21,"end":40}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform_substitutions` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d72ee6aae32c9c1901f52bf269676a8b","location":{"path":"lib/gitlab/quick_actions/extractor.rb","lines":{"begin":115,"end":134}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `commands_regex` has 30 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"1bf5c2a5cbd5a2d9489e4fefc15a284e","location":{"path":"lib/gitlab/quick_actions/extractor.rb","lines":{"begin":63,"end":113}},"other_locations":[],"remediation_points":720000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `to_h` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a75d7faca54c18a6777848a3850d38cc","location":{"path":"lib/gitlab/quick_actions/command_definition.rb","lines":{"begin":49,"end":66}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `wait` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bf93e6de620ccd0324ee0deae40e9661","location":{"path":"lib/gitlab/job_waiter.rb","lines":{"begin":44,"end":72}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `validate_number_of_plurals` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"ca29b997f9707951ab1c16832eac75dd","location":{"path":"lib/gitlab/i18n/po_linter.rb","lines":{"begin":86,"end":94}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `fill_in_variables` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7baf89f0f9dd60c092411c5d3c85f5c4","location":{"path":"lib/gitlab/i18n/po_linter.rb","lines":{"begin":222,"end":236}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `PoLinter` has 22 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"befcb8a3c9cdc94613a938be76c2abf4","location":{"path":"lib/gitlab/i18n/po_linter.rb","lines":{"begin":3,"end":276}},"other_locations":[],"remediation_points":1400000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `missing_members?` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"a92dd0b78adf6408e1bb1362dfd52ac0","location":{"path":"lib/gitlab/background_migration/create_fork_network_memberships_range.rb","lines":{"begin":48,"end":78}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `migrate_stage_index_sql` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"17594a98741c619d81f5982d56852229","location":{"path":"lib/gitlab/background_migration/migrate_stage_index.rb","lines":{"begin":15,"end":44}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `remove_restricted_features_todos` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2f6311086002a50283e8edb15d2db097","location":{"path":"lib/gitlab/background_migration/remove_restricted_todos.rb","lines":{"begin":80,"end":94}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `perform` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"3b81cbbe6df91a033cc8895bc63aa0e7","location":{"path":"lib/gitlab/background_migration/copy_column.rb","lines":{"begin":17,"end":17}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `each_pair` has a Cognitive Complexity of 43 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d8eeaa32395230adf97a240f222c4203","location":{"path":"lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb","lines":{"begin":53,"end":189}},"other_locations":[],"remediation_points":3950000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `initialize_array` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7098a1e02746bd3280879e695e913e37","location":{"path":"lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb","lines":{"begin":262,"end":276}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `perform` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bb5327ee9231b73596539ad7b2c167fa","location":{"path":"lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb","lines":{"begin":300,"end":314}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `each_pair` has 131 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"d03740b18e02c4d6c12a846688367eb2","location":{"path":"lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb","lines":{"begin":53,"end":189}},"other_locations":[],"remediation_points":3144000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `single_diff_rows` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8c44e90db62947f73461b513d8b2bf71","location":{"path":"lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb","lines":{"begin":86,"end":129}},"other_locations":[],"remediation_points":750000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `single_diff_rows` has 32 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"bc4293178ed4b9812cc3752d52c4c2ae","location":{"path":"lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb","lines":{"begin":86,"end":129}},"other_locations":[],"remediation_points":768000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `median` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8131f9850b467befa49f83c8893e074e","location":{"path":"lib/gitlab/cycle_analytics/base_stage.rb","lines":{"begin":23,"end":46}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `first_time_reference_commit` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"57863573b761b600f6f37177c4bdcc2c","location":{"path":"lib/gitlab/cycle_analytics/plan_event_fetcher.rb","lines":{"begin":40,"end":52}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `groups` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"bb94f91ea8ea9bfebe88077bde7c9c70","location":{"path":"lib/gitlab/blame.rb","lines":{"begin":10,"end":34}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `issuable_meta_data` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"a722790729116e77942fe04c5ea5c2fe","location":{"path":"lib/gitlab/issuable_metadata.rb","lines":{"begin":3,"end":42}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `issuable_meta_data` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"654ad0544dd20bd2a0fa2b2222724814","location":{"path":"lib/gitlab/issuable_metadata.rb","lines":{"begin":3,"end":42}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find_id_by_path` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"249edd9d3a49a4634c75d7f66eddf0d3","location":{"path":"lib/gitlab/git/tree.rb","lines":{"begin":36,"end":52}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `get_submodules_by_name` has a Cognitive Complexity of 16 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1779cdd505003a594a0ec1e401b49750","location":{"path":"lib/gitlab/git/gitmodules_parser.rb","lines":{"begin":45,"end":66}},"other_locations":[],"remediation_points":1250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `merge` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"14d8d23268624da10eee3aa315d71171","location":{"path":"lib/gitlab/git/repository.rb","lines":{"begin":552,"end":552}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `find_commits_by_message` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"03db09f73721e8dc853a0e8d5c5b586a","location":{"path":"lib/gitlab/git/repository.rb","lines":{"begin":948,"end":948}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `repository.rb` has 724 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"4fd0011d1b240dc0bd3776dada53d992","location":{"path":"lib/gitlab/git/repository.rb","lines":{"begin":1,"end":1045}},"other_locations":[],"remediation_points":8025600,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `refs_hash` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"1b3b8f358aa6754b1127fcaf1fc63eab","location":{"path":"lib/gitlab/git/repository.rb","lines":{"begin":461,"end":473}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `process_count_commits_options` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d7dfc3dabbbd3403a850821e31ef9869","location":{"path":"lib/gitlab/git/repository.rb","lines":{"begin":987,"end":1005}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Repository` has 117 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"7e5d4a5ee3414c57e7d1ae875324fab4","location":{"path":"lib/gitlab/git/repository.rb","lines":{"begin":7,"end":1043}},"other_locations":[],"remediation_points":10900000,"severity":"major","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `resolve_lines` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.","fingerprint":"2a9fd4db8c8cf162f378aa963d6765b0","location":{"path":"lib/gitlab/git/conflict/file.rb","lines":{"begin":64,"end":86}},"other_locations":[],"remediation_points":1350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `parse` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"244c77bd66325eb47245517103368219","location":{"path":"lib/gitlab/git/conflict/parser.rb","lines":{"begin":15,"end":70}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `parse` has 44 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"bf9628db17f21b3c6afe90328a37f6d0","location":{"path":"lib/gitlab/git/conflict/parser.rb","lines":{"begin":15,"end":70}},"other_locations":[],"remediation_points":1056000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8c678916b00a357376bbd89e7d450a28","location":{"path":"lib/gitlab/git/blob.rb","lines":{"begin":24,"end":50}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `parse_attributes` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"4edbf1e31d321135c7006513a4035f15","location":{"path":"lib/gitlab/git/attributes_parser.rb","lines":{"begin":41,"end":77}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `parse_data` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"19a8edffd98f8988b199c80f6919a68a","location":{"path":"lib/gitlab/git/attributes_parser.rb","lines":{"begin":91,"end":108}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `update_page` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"cc6e53b3d35661118499e22b31c33048","location":{"path":"lib/gitlab/git/wiki.rb","lines":{"begin":41,"end":41}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `gitaly_update_page` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"29ae202c7d73dff34ed176c4c489e03c","location":{"path":"lib/gitlab/git/wiki.rb","lines":{"begin":139,"end":139}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Wiki` has 25 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"16108ddc899f6bed5535bed2d4dbd403","location":{"path":"lib/gitlab/git/wiki.rb","lines":{"begin":3,"end":166}},"other_locations":[],"remediation_points":1700000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `initialize` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"992ce430fa1cfa75fffb04b791835530","location":{"path":"lib/gitlab/git/compare.rb","lines":{"begin":8,"end":22}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"file_lines","content":{"body":""},"description":"File `commit.rb` has 261 lines of code (exceeds 250 allowed). Consider refactoring.","fingerprint":"b0e8a3e8bec1d1e4d61a4ca6961eee37","location":{"path":"lib/gitlab/git/commit.rb","lines":{"begin":2,"end":421}},"other_locations":[],"remediation_points":1358400,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `find` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d2501d4f37da4c3f3bfc13d8ddefb8b1","location":{"path":"lib/gitlab/git/commit.rb","lines":{"begin":52,"end":72}},"other_locations":[],"remediation_points":650000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Commit` has 48 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"5c05f17b105a544fbb7b1e653e4e9622","location":{"path":"lib/gitlab/git/commit.rb","lines":{"begin":4,"end":419}},"other_locations":[],"remediation_points":4000000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `each_serialized_patch` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.","fingerprint":"489c6b7588d70b32aa203aa6c41632df","location":{"path":"lib/gitlab/git/diff_collection.rb","lines":{"begin":120,"end":152}},"other_locations":[],"remediation_points":850000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `between` has 5 arguments (exceeds 4 allowed). Consider refactoring.","fingerprint":"4c22ec0e0d894fff0c7dd7383e7e90f2","location":{"path":"lib/gitlab/git/diff.rb","lines":{"begin":31,"end":31}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `process_raw_blame` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"c5cb9f5c58bb1d9925a29d6754967c20","location":{"path":"lib/gitlab/git/blame.rb","lines":{"begin":30,"end":56}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `in_lock` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"68b34982cd05d58d79738b15c96f20f7","location":{"path":"lib/gitlab/exclusive_lease_helpers.rb","lines":{"begin":12,"end":27}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `run_check` has a Cognitive Complexity of 18 (exceeds 5 allowed). Consider refactoring.","fingerprint":"d86f72edc80623f2c31df045a73c54a3","location":{"path":"lib/system_check/simple_executor.rb","lines":{"begin":45,"end":82}},"other_locations":[],"remediation_points":1450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `run_check` has 27 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"32ee25468fc9ba9255e373bf8565a409","location":{"path":"lib/system_check/simple_executor.rb","lines":{"begin":45,"end":82}},"other_locations":[],"remediation_points":648000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `dump` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"90c4b195bc0f9e6e440c936a49f66db4","location":{"path":"lib/backup/database.rb","lines":{"begin":14,"end":48}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `dump` has 28 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"e224fd061ebe87897322d771a18ed4f9","location":{"path":"lib/backup/database.rb","lines":{"begin":14,"end":48}},"other_locations":[],"remediation_points":672000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `dump` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.","fingerprint":"7e1d28a077c128dd08f453c105fdd9df","location":{"path":"lib/backup/repository.rb","lines":{"begin":11,"end":39}},"other_locations":[],"remediation_points":550000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `restore` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.","fingerprint":"8c618e2d28195eb4c364400e1b377f3f","location":{"path":"lib/backup/repository.rb","lines":{"begin":74,"end":119}},"other_locations":[],"remediation_points":1050000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `restore` has 38 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"dce1e21850a52796bdc54dd0352fef39","location":{"path":"lib/backup/repository.rb","lines":{"begin":74,"end":119}},"other_locations":[],"remediation_points":912000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `cleanup` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","fingerprint":"df1565e2084a026c19a57df20f8c1dab","location":{"path":"lib/backup/manager.rb","lines":{"begin":59,"end":72}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `remove_old` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.","fingerprint":"25cd2cf6fbb64afd366f3be6ab9d89fd","location":{"path":"lib/backup/manager.rb","lines":{"begin":74,"end":107}},"other_locations":[],"remediation_points":1350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `unpack` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.","fingerprint":"67d7ec9e6873944629ee60ae0c82b98f","location":{"path":"lib/backup/manager.rb","lines":{"begin":110,"end":161}},"other_locations":[],"remediation_points":1350000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_count","content":{"body":""},"description":"Class `Manager` has 21 methods (exceeds 20 allowed). Consider refactoring.","fingerprint":"c011f3bde58a57c5c1b310f99fdde1e8","location":{"path":"lib/backup/manager.rb","lines":{"begin":2,"end":247}},"other_locations":[],"remediation_points":1300000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `unpack` has 41 lines of code (exceeds 25 allowed). Consider refactoring.","fingerprint":"0f4697ee5870ab55f0f0482a90f0aafe","location":{"path":"lib/backup/manager.rb","lines":{"begin":110,"end":161}},"other_locations":[],"remediation_points":984000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `decoded` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"9d399e9cd55ad1c8999c18172266e16d","location":{"path":"lib/omni_auth/strategies/jwt.rb","lines":{"begin":37,"end":51}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `class_for_class` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.","fingerprint":"44146c776b1d2705ab1e91d14f931ca6","location":{"path":"lib/declarative_policy.rb","lines":{"begin":62,"end":74}},"other_locations":[],"remediation_points":250000,"severity":"minor","type":"issue","engine_name":"structure"},{"categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `compute_class_for_class` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","fingerprint":"504ef7ee6e8fa9917bb219a3ce5a6531","location":{"path":"lib/declarative_policy.rb","lines":{"begin":76,"end":93}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue","engine_name":"structure"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/auth/ldap/dn.rb","lines":{"begin":23,"end":298}},"remediation_points":14090000,"other_locations":[{"path":"lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb","lines":{"begin":18,"end":293}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 722**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1672855f6e5f2e5e61f5f47620f50c44","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb","lines":{"begin":18,"end":293}},"remediation_points":14090000,"other_locations":[{"path":"lib/gitlab/auth/ldap/dn.rb","lines":{"begin":23,"end":298}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 722**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"08b04488cb6c1d433518d22f9fd7ac22","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 6 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":8,"end":20}},"remediation_points":1270000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":38,"end":38}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":45,"end":45}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":65,"end":65}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":73,"end":73}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":96,"end":96}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d3612d232382cae9828e7733da5a69ff","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 6 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":38,"end":38}},"remediation_points":1270000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":8,"end":20}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":45,"end":45}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":65,"end":65}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":73,"end":73}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":96,"end":96}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d3612d232382cae9828e7733da5a69ff","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 6 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":45,"end":45}},"remediation_points":1270000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":8,"end":20}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":38,"end":38}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":65,"end":65}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":73,"end":73}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":96,"end":96}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d3612d232382cae9828e7733da5a69ff","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 6 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":65,"end":65}},"remediation_points":1270000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":8,"end":20}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":38,"end":38}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":45,"end":45}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":73,"end":73}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":96,"end":96}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d3612d232382cae9828e7733da5a69ff","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 6 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":73,"end":73}},"remediation_points":1270000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":8,"end":20}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":38,"end":38}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":45,"end":45}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":65,"end":65}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":96,"end":96}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d3612d232382cae9828e7733da5a69ff","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 6 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":96,"end":96}},"remediation_points":1270000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":8,"end":20}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":38,"end":38}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":45,"end":45}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":65,"end":65}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":73,"end":73}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d3612d232382cae9828e7733da5a69ff","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 5 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":47,"end":47}},"remediation_points":1170000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":61,"end":61}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":64,"end":64}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":74,"end":74}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":81,"end":81}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 76**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9d3f720528b273e90fa0c600a4007367","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 5 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":61,"end":61}},"remediation_points":1170000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":47,"end":47}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":64,"end":64}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":74,"end":74}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":81,"end":81}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 76**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9d3f720528b273e90fa0c600a4007367","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 5 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":64,"end":64}},"remediation_points":1170000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":47,"end":47}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":61,"end":61}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":74,"end":74}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":81,"end":81}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 76**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9d3f720528b273e90fa0c600a4007367","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 5 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":74,"end":74}},"remediation_points":1170000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":47,"end":47}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":61,"end":61}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":64,"end":64}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":81,"end":81}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 76**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9d3f720528b273e90fa0c600a4007367","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 5 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":81,"end":81}},"remediation_points":1170000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":47,"end":47}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":61,"end":61}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":64,"end":64}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":74,"end":74}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 76**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9d3f720528b273e90fa0c600a4007367","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":87,"end":87}},"remediation_points":1290000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":88,"end":88}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 82**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3bfe7daee55e5a8099dd7a73db3a63a6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":88,"end":88}},"remediation_points":1290000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":87,"end":87}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 82**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3bfe7daee55e5a8099dd7a73db3a63a6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":41,"end":41}},"remediation_points":1130000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":42,"end":42}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":58,"end":58}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":59,"end":59}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 74**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8f8e7cbf1a02ae8fa5ae5397530797b7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":42,"end":42}},"remediation_points":1130000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":41,"end":41}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":58,"end":58}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":59,"end":59}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 74**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8f8e7cbf1a02ae8fa5ae5397530797b7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":58,"end":58}},"remediation_points":1130000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":41,"end":41}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":42,"end":42}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":59,"end":59}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 74**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8f8e7cbf1a02ae8fa5ae5397530797b7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":59,"end":59}},"remediation_points":1130000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":41,"end":41}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":42,"end":42}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":58,"end":58}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 74**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8f8e7cbf1a02ae8fa5ae5397530797b7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":30,"end":30}},"remediation_points":1610000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":31,"end":31}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":95,"end":95}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 98**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"22a2073b162b635650c424f759625ae1","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":31,"end":31}},"remediation_points":1610000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":30,"end":30}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":95,"end":95}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 98**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"22a2073b162b635650c424f759625ae1","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":95,"end":95}},"remediation_points":1610000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":30,"end":30}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":31,"end":31}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 98**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"22a2073b162b635650c424f759625ae1","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/clusters/platforms/kubernetes.rb","lines":{"begin":65,"end":78}},"remediation_points":930000,"other_locations":[{"path":"app/models/project_services/kubernetes_service.rb","lines":{"begin":107,"end":120}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"4b2ec9ed4e92599e6bbba9a4a97ebeb0","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/project_services/kubernetes_service.rb","lines":{"begin":107,"end":120}},"remediation_points":930000,"other_locations":[{"path":"app/models/clusters/platforms/kubernetes.rb","lines":{"begin":65,"end":78}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0c7ce0e1a2c6b9abe1956a85bf8e6e52","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":40,"end":40}},"remediation_points":1350000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":72,"end":72}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":78,"end":78}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 85**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2f55aade2bccc69344fa093ec61a30db","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":72,"end":72}},"remediation_points":1350000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":40,"end":40}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":78,"end":78}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 85**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2f55aade2bccc69344fa093ec61a30db","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":78,"end":78}},"remediation_points":1350000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":40,"end":40}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":72,"end":72}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 85**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2f55aade2bccc69344fa093ec61a30db","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":75,"end":75}},"remediation_points":1230000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":79,"end":79}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":97,"end":97}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 79**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9473b40c2cb0f7afc9308169cf2dced6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":79,"end":79}},"remediation_points":1230000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":75,"end":75}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":97,"end":97}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 79**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9473b40c2cb0f7afc9308169cf2dced6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":97,"end":97}},"remediation_points":1230000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":75,"end":75}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":79,"end":79}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 79**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9473b40c2cb0f7afc9308169cf2dced6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":35,"end":35}},"remediation_points":1090000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":43,"end":43}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":63,"end":63}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 72**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6149a95705035fa65fb27a491aa16e7d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":43,"end":43}},"remediation_points":1090000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":35,"end":35}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":63,"end":63}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 72**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6149a95705035fa65fb27a491aa16e7d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":63,"end":63}},"remediation_points":1090000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":35,"end":35}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":43,"end":43}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 72**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6149a95705035fa65fb27a491aa16e7d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":49,"end":49}},"remediation_points":1070000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":56,"end":56}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":83,"end":83}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 71**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"123ff3d56c152101a1c911f1a95326be","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":56,"end":56}},"remediation_points":1070000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":49,"end":49}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":83,"end":83}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 71**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"123ff3d56c152101a1c911f1a95326be","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":83,"end":83}},"remediation_points":1070000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":49,"end":49}},{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":56,"end":56}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 71**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"123ff3d56c152101a1c911f1a95326be","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":198,"end":227}},"remediation_points":570000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":270,"end":299}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 46**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"672d4e588f6155fa74bf7ca34fedabb2","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":270,"end":299}},"remediation_points":570000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":198,"end":227}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 46**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"672d4e588f6155fa74bf7ca34fedabb2","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":101,"end":101}},"remediation_points":1490000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":103,"end":103}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 92**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"633c7fa21c24339ba725403293231a75","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":103,"end":103}},"remediation_points":1490000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":101,"end":101}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 92**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"633c7fa21c24339ba725403293231a75","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":90,"end":90}},"remediation_points":1390000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":102,"end":102}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 87**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"925e4b3663b9cd9937ee7db7f882dfbc","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":102,"end":102}},"remediation_points":1390000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":90,"end":90}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 87**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"925e4b3663b9cd9937ee7db7f882dfbc","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":55,"end":55}},"remediation_points":1250000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":89,"end":89}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 80**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b931145ce6d6777daae248a043f04d6d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":89,"end":89}},"remediation_points":1250000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":55,"end":55}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 80**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b931145ce6d6777daae248a043f04d6d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/bitbucket/collection.rb","lines":{"begin":2,"end":18}},"remediation_points":410000,"other_locations":[{"path":"lib/bitbucket_server/collection.rb","lines":{"begin":4,"end":20}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 38**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9daf3a24f43b64dd40b3841c26c29df5","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/bitbucket_server/collection.rb","lines":{"begin":4,"end":20}},"remediation_points":410000,"other_locations":[{"path":"lib/bitbucket/collection.rb","lines":{"begin":2,"end":18}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 38**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ebc02a5c26ee206388b48b53d93e24a1","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":40,"end":89}},"remediation_points":1150000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":91,"end":140}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 75**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6778d05c8ddc9f6653765ac7ae623751","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":91,"end":140}},"remediation_points":1150000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":40,"end":89}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 75**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6778d05c8ddc9f6653765ac7ae623751","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/git/tag.rb","lines":{"begin":14,"end":24}},"remediation_points":650000,"other_locations":[{"path":"lib/gitlab/git/commit.rb","lines":{"begin":159,"end":169}},{"path":"lib/gitlab/git/commit.rb","lines":{"begin":179,"end":189}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d27c41bdaed8bbbc1c91788baad098b2","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/git/commit.rb","lines":{"begin":159,"end":169}},"remediation_points":650000,"other_locations":[{"path":"lib/gitlab/git/commit.rb","lines":{"begin":179,"end":189}},{"path":"lib/gitlab/git/tag.rb","lines":{"begin":14,"end":24}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e87685897f699d56f0c9bba98c92c792","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/git/commit.rb","lines":{"begin":179,"end":189}},"remediation_points":650000,"other_locations":[{"path":"lib/gitlab/git/commit.rb","lines":{"begin":159,"end":169}},{"path":"lib/gitlab/git/tag.rb","lines":{"begin":14,"end":24}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e87685897f699d56f0c9bba98c92c792","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":46,"end":46}},"remediation_points":1150000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":80,"end":80}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 75**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"482f209ad35861d8a59e5e60275568d2","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":80,"end":80}},"remediation_points":1150000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":46,"end":46}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 75**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"482f209ad35861d8a59e5e60275568d2","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"qa/spec/git/location_spec.rb","lines":{"begin":3,"end":24}},"remediation_points":1130000,"other_locations":[{"path":"qa/spec/git/location_spec.rb","lines":{"begin":29,"end":50}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 74**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"dd9851dda741bf04f8f991425e6b4fa8","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"qa/spec/git/location_spec.rb","lines":{"begin":29,"end":50}},"remediation_points":1130000,"other_locations":[{"path":"qa/spec/git/location_spec.rb","lines":{"begin":3,"end":24}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 74**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"dd9851dda741bf04f8f991425e6b4fa8","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":54,"end":54}},"remediation_points":1110000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":66,"end":66}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 73**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"62cf424fda0f778b3dcc5914fc2096b6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":66,"end":66}},"remediation_points":1110000,"other_locations":[{"path":"lib/gitlab/sanitizers/svg/whitelist.rb","lines":{"begin":54,"end":54}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 73**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"62cf424fda0f778b3dcc5914fc2096b6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/system_hooks.rb","lines":{"begin":24,"end":31}},"remediation_points":550000,"other_locations":[{"path":"lib/api/broadcast_messages.rb","lines":{"begin":66,"end":73}},{"path":"lib/api/pipeline_schedules.rb","lines":{"begin":66,"end":73}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"dc8d377ac78b1e411b24adff2bb25ac8","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/broadcast_messages.rb","lines":{"begin":66,"end":73}},"remediation_points":550000,"other_locations":[{"path":"lib/api/pipeline_schedules.rb","lines":{"begin":66,"end":73}},{"path":"lib/api/system_hooks.rb","lines":{"begin":24,"end":31}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d6ed9adeccc2432c442bb7e3b2de77ca","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/pipeline_schedules.rb","lines":{"begin":66,"end":73}},"remediation_points":550000,"other_locations":[{"path":"lib/api/broadcast_messages.rb","lines":{"begin":66,"end":73}},{"path":"lib/api/system_hooks.rb","lines":{"begin":24,"end":31}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e53239021b533bc772fd7c38fd9fbf32","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/http_io.rb","lines":{"begin":76,"end":99}},"remediation_points":930000,"other_locations":[{"path":"lib/gitlab/ci/trace/chunked_io.rb","lines":{"begin":69,"end":92}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"06b83fddd1854f4cbb5ee692f2b3c479","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/ci/trace/chunked_io.rb","lines":{"begin":69,"end":92}},"remediation_points":930000,"other_locations":[{"path":"lib/gitlab/http_io.rb","lines":{"begin":76,"end":99}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"fa155c048bfa229583a4b14cba8ef368","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/controllers/projects/commits_controller.rb","lines":{"begin":38,"end":49}},"remediation_points":270000,"other_locations":[{"path":"app/controllers/projects/compare_controller.rb","lines":{"begin":47,"end":58}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"25a210eb00ee75fb3a27aef364105444","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/controllers/projects/compare_controller.rb","lines":{"begin":47,"end":58}},"remediation_points":270000,"other_locations":[{"path":"app/controllers/projects/commits_controller.rb","lines":{"begin":38,"end":49}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3331c85dad0745b1770318b847052656","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/clusters/platforms/kubernetes.rb","lines":{"begin":87,"end":91}},"remediation_points":270000,"other_locations":[{"path":"app/models/project_services/kubernetes_service.rb","lines":{"begin":129,"end":133}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"a485030c77dac9d4720e17783248f6f2","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/project_services/kubernetes_service.rb","lines":{"begin":129,"end":133}},"remediation_points":270000,"other_locations":[{"path":"app/models/clusters/platforms/kubernetes.rb","lines":{"begin":87,"end":91}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"70c3aa57163e28d9e2b8cf58b891e37a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/clusters/platforms/kubernetes.rb","lines":{"begin":153,"end":161}},"remediation_points":230000,"other_locations":[{"path":"app/models/project_services/kubernetes_service.rb","lines":{"begin":209,"end":217}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c29d81ebf7e5c4008896a514ac05cd90","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/project_services/kubernetes_service.rb","lines":{"begin":209,"end":217}},"remediation_points":230000,"other_locations":[{"path":"app/models/clusters/platforms/kubernetes.rb","lines":{"begin":153,"end":161}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"562f28dc8f9527c3acb9451bb9905421","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"rubocop/cop/code_reuse/presenter.rb","lines":{"begin":5,"end":36}},"remediation_points":770000,"other_locations":[{"path":"rubocop/cop/code_reuse/serializer.rb","lines":{"begin":5,"end":36}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 56**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ee8707c802bad394fc157d1bd785ea38","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"rubocop/cop/code_reuse/serializer.rb","lines":{"begin":5,"end":36}},"remediation_points":770000,"other_locations":[{"path":"rubocop/cop/code_reuse/presenter.rb","lines":{"begin":5,"end":36}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 56**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"999d054331f9bf60e9fac34f362dfa33","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/gitaly_client/commit_service.rb","lines":{"begin":363,"end":376}},"remediation_points":730000,"other_locations":[{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":217,"end":230}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 54**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6d5fdcb9f4e92bdc8de77ce0d327e38d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":217,"end":230}},"remediation_points":730000,"other_locations":[{"path":"lib/gitlab/gitaly_client/commit_service.rb","lines":{"begin":363,"end":376}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 54**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b9094b2f402bdf758fbd32cced243c48","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/clusters/platforms/kubernetes.rb","lines":{"begin":143,"end":151}},"remediation_points":170000,"other_locations":[{"path":"app/models/project_services/kubernetes_service.rb","lines":{"begin":199,"end":207}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9cdba14c6ea64993cb19a2d65e8cf92c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/project_services/kubernetes_service.rb","lines":{"begin":199,"end":207}},"remediation_points":170000,"other_locations":[{"path":"app/models/clusters/platforms/kubernetes.rb","lines":{"begin":143,"end":151}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"a53ce766f9ff3869e29a216c2dd49425","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/controllers/users_controller.rb","lines":{"begin":37,"end":46}},"remediation_points":150000,"other_locations":[{"path":"app/controllers/users_controller.rb","lines":{"begin":50,"end":59}},{"path":"app/controllers/users_controller.rb","lines":{"begin":63,"end":72}},{"path":"app/controllers/users_controller.rb","lines":{"begin":76,"end":85}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7de6970da5143bb219d1d73c65245a18","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/controllers/users_controller.rb","lines":{"begin":50,"end":59}},"remediation_points":150000,"other_locations":[{"path":"app/controllers/users_controller.rb","lines":{"begin":37,"end":46}},{"path":"app/controllers/users_controller.rb","lines":{"begin":63,"end":72}},{"path":"app/controllers/users_controller.rb","lines":{"begin":76,"end":85}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7de6970da5143bb219d1d73c65245a18","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/controllers/users_controller.rb","lines":{"begin":63,"end":72}},"remediation_points":150000,"other_locations":[{"path":"app/controllers/users_controller.rb","lines":{"begin":37,"end":46}},{"path":"app/controllers/users_controller.rb","lines":{"begin":50,"end":59}},{"path":"app/controllers/users_controller.rb","lines":{"begin":76,"end":85}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7de6970da5143bb219d1d73c65245a18","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/controllers/users_controller.rb","lines":{"begin":76,"end":85}},"remediation_points":150000,"other_locations":[{"path":"app/controllers/users_controller.rb","lines":{"begin":37,"end":46}},{"path":"app/controllers/users_controller.rb","lines":{"begin":50,"end":59}},{"path":"app/controllers/users_controller.rb","lines":{"begin":63,"end":72}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7de6970da5143bb219d1d73c65245a18","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/serializers/note_entity.rb","lines":{"begin":67,"end":68}},"remediation_points":150000,"other_locations":[{"path":"app/serializers/project_note_entity.rb","lines":{"begin":20,"end":21}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3177b0cd54cd809575f1d61e0a65ac25","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/serializers/project_note_entity.rb","lines":{"begin":20,"end":21}},"remediation_points":150000,"other_locations":[{"path":"app/serializers/note_entity.rb","lines":{"begin":67,"end":68}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"bc28db1513fbf8f22f0e63c12ccb87bb","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/project_hooks.rb","lines":{"begin":66,"end":69}},"remediation_points":150000,"other_locations":[{"path":"lib/api/project_hooks.rb","lines":{"begin":88,"end":91}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"cd274943b0a85d56266ca30e0dc9e6ce","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/project_hooks.rb","lines":{"begin":88,"end":91}},"remediation_points":150000,"other_locations":[{"path":"lib/api/project_hooks.rb","lines":{"begin":66,"end":69}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"cd274943b0a85d56266ca30e0dc9e6ce","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/settings.rb","lines":{"begin":76,"end":83}},"remediation_points":650000,"other_locations":[{"path":"lib/api/runner.rb","lines":{"begin":86,"end":93}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ad9527e40decd629ae978623c4e6dbb3","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/runner.rb","lines":{"begin":86,"end":93}},"remediation_points":650000,"other_locations":[{"path":"lib/api/settings.rb","lines":{"begin":76,"end":83}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5a97fc139bd16e6c2c1ebb387c258035","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/commits.rb","lines":{"begin":28,"end":36}},"remediation_points":590000,"other_locations":[{"path":"lib/api/users.rb","lines":{"begin":182,"end":190}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 47**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"4a0f4dd71fe015ff3e29d7ce0c5f5aa0","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/users.rb","lines":{"begin":182,"end":190}},"remediation_points":590000,"other_locations":[{"path":"lib/api/commits.rb","lines":{"begin":28,"end":36}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 47**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"656c922dc9f4dab2a9d07fc10e49f190","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/health_checks/redis/queues_check.rb","lines":{"begin":2,"end":25}},"remediation_points":270000,"other_locations":[{"path":"lib/gitlab/health_checks/redis/cache_check.rb","lines":{"begin":2,"end":25}},{"path":"lib/gitlab/health_checks/redis/shared_state_check.rb","lines":{"begin":2,"end":25}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"a52f4db116344fb82ae762dfb265af18","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/health_checks/redis/cache_check.rb","lines":{"begin":2,"end":25}},"remediation_points":270000,"other_locations":[{"path":"lib/gitlab/health_checks/redis/queues_check.rb","lines":{"begin":2,"end":25}},{"path":"lib/gitlab/health_checks/redis/shared_state_check.rb","lines":{"begin":2,"end":25}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"31c92fe357e3684090c3440aef2d0b29","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/health_checks/redis/shared_state_check.rb","lines":{"begin":2,"end":25}},"remediation_points":270000,"other_locations":[{"path":"lib/gitlab/health_checks/redis/cache_check.rb","lines":{"begin":2,"end":25}},{"path":"lib/gitlab/health_checks/redis/queues_check.rb","lines":{"begin":2,"end":25}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"fed5fac4efc814d8bd6d479976af387a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/helpers/milestones_helper.rb","lines":{"begin":199,"end":206}},"remediation_points":250000,"other_locations":[{"path":"app/helpers/milestones_helper.rb","lines":{"begin":209,"end":216}},{"path":"app/helpers/milestones_helper.rb","lines":{"begin":219,"end":226}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 30**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9bdbae2675861886dd810a67f252ae76","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/helpers/milestones_helper.rb","lines":{"begin":209,"end":216}},"remediation_points":250000,"other_locations":[{"path":"app/helpers/milestones_helper.rb","lines":{"begin":199,"end":206}},{"path":"app/helpers/milestones_helper.rb","lines":{"begin":219,"end":226}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 30**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9bdbae2675861886dd810a67f252ae76","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/helpers/milestones_helper.rb","lines":{"begin":219,"end":226}},"remediation_points":250000,"other_locations":[{"path":"app/helpers/milestones_helper.rb","lines":{"begin":199,"end":206}},{"path":"app/helpers/milestones_helper.rb","lines":{"begin":209,"end":216}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 30**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9bdbae2675861886dd810a67f252ae76","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/policies/project_policy.rb","lines":{"begin":217,"end":236}},"remediation_points":510000,"other_locations":[{"path":"app/policies/project_policy.rb","lines":{"begin":330,"end":351}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 43**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6f53dee2e0dbe6e42301dfabd181bed2","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/policies/project_policy.rb","lines":{"begin":330,"end":351}},"remediation_points":510000,"other_locations":[{"path":"app/policies/project_policy.rb","lines":{"begin":217,"end":236}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 43**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6f53dee2e0dbe6e42301dfabd181bed2","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":5,"end":22}},"remediation_points":210000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":250,"end":267}},{"path":"lib/api/services.rb","lines":{"begin":322,"end":339}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"14eeea8dcc69e4efb8f98042eb7ee62c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":250,"end":267}},"remediation_points":210000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":5,"end":22}},{"path":"lib/api/services.rb","lines":{"begin":322,"end":339}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"14eeea8dcc69e4efb8f98042eb7ee62c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":322,"end":339}},"remediation_points":210000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":5,"end":22}},{"path":"lib/api/services.rb","lines":{"begin":250,"end":267}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"14eeea8dcc69e4efb8f98042eb7ee62c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":230,"end":247}},"remediation_points":210000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":302,"end":319}},{"path":"lib/api/services.rb","lines":{"begin":511,"end":528}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"14eeea8dcc69e4efb8f98042eb7ee62c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":302,"end":319}},"remediation_points":210000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":230,"end":247}},{"path":"lib/api/services.rb","lines":{"begin":511,"end":528}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"14eeea8dcc69e4efb8f98042eb7ee62c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":511,"end":528}},"remediation_points":210000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":230,"end":247}},{"path":"lib/api/services.rb","lines":{"begin":302,"end":319}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"14eeea8dcc69e4efb8f98042eb7ee62c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/http_io.rb","lines":{"begin":102,"end":119}},"remediation_points":490000,"other_locations":[{"path":"lib/gitlab/ci/trace/chunked_io.rb","lines":{"begin":95,"end":112}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 42**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9fcdc086aaa42b293828e5799976b36c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/ci/trace/chunked_io.rb","lines":{"begin":95,"end":112}},"remediation_points":490000,"other_locations":[{"path":"lib/gitlab/http_io.rb","lines":{"begin":102,"end":119}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 42**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0aa489542536e966d0d4ee9d92b86de3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/policies/project_policy.rb","lines":{"begin":152,"end":170}},"remediation_points":470000,"other_locations":[{"path":"app/policies/project_policy.rb","lines":{"begin":177,"end":195}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 41**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3960d89aa99311987411af9a2ce4316a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/policies/project_policy.rb","lines":{"begin":177,"end":195}},"remediation_points":470000,"other_locations":[{"path":"app/policies/project_policy.rb","lines":{"begin":152,"end":170}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 41**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3960d89aa99311987411af9a2ce4316a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/tags.rb","lines":{"begin":95,"end":104}},"remediation_points":470000,"other_locations":[{"path":"lib/api/tags.rb","lines":{"begin":115,"end":124}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 41**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"04627bf7d430fe80b4f7d86af94fb085","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/tags.rb","lines":{"begin":115,"end":124}},"remediation_points":470000,"other_locations":[{"path":"lib/api/tags.rb","lines":{"begin":95,"end":104}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 41**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"04627bf7d430fe80b4f7d86af94fb085","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/issues.rb","lines":{"begin":159,"end":171}},"remediation_points":450000,"other_locations":[{"path":"lib/api/commit_statuses.rb","lines":{"begin":16,"end":23}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 40**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"72cad3326805b4987fbe6343ebb3d0fd","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/commit_statuses.rb","lines":{"begin":16,"end":23}},"remediation_points":450000,"other_locations":[{"path":"lib/api/issues.rb","lines":{"begin":159,"end":171}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 40**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"69ab1e3ccd645591451e705830d55186","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/discussions.rb","lines":{"begin":159,"end":163}},"remediation_points":170000,"other_locations":[{"path":"lib/api/discussions.rb","lines":{"begin":194,"end":198}},{"path":"lib/api/discussions.rb","lines":{"begin":209,"end":213}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"94185a2a13c6fd98f5095f170db11d97","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/discussions.rb","lines":{"begin":194,"end":198}},"remediation_points":170000,"other_locations":[{"path":"lib/api/discussions.rb","lines":{"begin":159,"end":163}},{"path":"lib/api/discussions.rb","lines":{"begin":209,"end":213}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"94185a2a13c6fd98f5095f170db11d97","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/discussions.rb","lines":{"begin":209,"end":213}},"remediation_points":170000,"other_locations":[{"path":"lib/api/discussions.rb","lines":{"begin":159,"end":163}},{"path":"lib/api/discussions.rb","lines":{"begin":194,"end":198}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"94185a2a13c6fd98f5095f170db11d97","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"rubocop/cop/migration/timestamps.rb","lines":{"begin":3,"end":22}},"remediation_points":430000,"other_locations":[{"path":"rubocop/cop/migration/remove_index.rb","lines":{"begin":3,"end":21}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 39**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c7a34cb081b464faf300442c9a9c35d6","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"rubocop/cop/migration/remove_index.rb","lines":{"begin":3,"end":21}},"remediation_points":430000,"other_locations":[{"path":"rubocop/cop/migration/timestamps.rb","lines":{"begin":3,"end":22}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 39**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1c3d38d7fee5df958c3e432d1dd17b38","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/users.rb","lines":{"begin":265,"end":272}},"remediation_points":150000,"other_locations":[{"path":"lib/api/users.rb","lines":{"begin":330,"end":337}},{"path":"lib/api/users.rb","lines":{"begin":417,"end":423}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b090709c230c91d97c8733ac43a69eb2","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/users.rb","lines":{"begin":330,"end":337}},"remediation_points":150000,"other_locations":[{"path":"lib/api/users.rb","lines":{"begin":265,"end":272}},{"path":"lib/api/users.rb","lines":{"begin":417,"end":423}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b090709c230c91d97c8733ac43a69eb2","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/users.rb","lines":{"begin":417,"end":423}},"remediation_points":150000,"other_locations":[{"path":"lib/api/users.rb","lines":{"begin":265,"end":272}},{"path":"lib/api/users.rb","lines":{"begin":330,"end":337}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b090709c230c91d97c8733ac43a69eb2","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":172,"end":195}},"remediation_points":390000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":645,"end":668}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 37**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ef781c60f85295b7fcab51d2c5fa6dbf","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":645,"end":668}},"remediation_points":390000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":172,"end":195}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 37**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ef781c60f85295b7fcab51d2c5fa6dbf","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":469,"end":492}},"remediation_points":390000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":599,"end":622}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 37**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ef781c60f85295b7fcab51d2c5fa6dbf","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/services.rb","lines":{"begin":599,"end":622}},"remediation_points":390000,"other_locations":[{"path":"lib/api/services.rb","lines":{"begin":469,"end":492}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 37**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ef781c60f85295b7fcab51d2c5fa6dbf","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/variables.rb","lines":{"begin":70,"end":80}},"remediation_points":390000,"other_locations":[{"path":"lib/api/group_variables.rb","lines":{"begin":70,"end":80}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 37**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b710502de22d6ac75887584b4089f55d","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/group_variables.rb","lines":{"begin":70,"end":80}},"remediation_points":390000,"other_locations":[{"path":"lib/api/variables.rb","lines":{"begin":70,"end":80}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 37**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5ed54d7e9dec3ef5264c44b21ab42f3b","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/concerns/relative_positioning.rb","lines":{"begin":63,"end":75}},"remediation_points":370000,"other_locations":[{"path":"app/models/concerns/relative_positioning.rb","lines":{"begin":78,"end":90}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 36**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6a6be26fc5b134ababae60c5b17185e7","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/concerns/relative_positioning.rb","lines":{"begin":78,"end":90}},"remediation_points":370000,"other_locations":[{"path":"app/models/concerns/relative_positioning.rb","lines":{"begin":63,"end":75}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 36**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6a6be26fc5b134ababae60c5b17185e7","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/users.rb","lines":{"begin":241,"end":252}},"remediation_points":370000,"other_locations":[{"path":"lib/api/users.rb","lines":{"begin":305,"end":316}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 36**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b03582b4029701d34f64ca56f039ff3c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/users.rb","lines":{"begin":305,"end":316}},"remediation_points":370000,"other_locations":[{"path":"lib/api/users.rb","lines":{"begin":241,"end":252}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 36**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b03582b4029701d34f64ca56f039ff3c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":615,"end":622}},"remediation_points":370000,"other_locations":[{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":706,"end":713}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 36**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6613d45047514c566568cdc6b20a1433","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":706,"end":713}},"remediation_points":370000,"other_locations":[{"path":"lib/gitlab/database/migration_helpers.rb","lines":{"begin":615,"end":622}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 36**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6613d45047514c566568cdc6b20a1433","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/banzai/filter/user_reference_filter.rb","lines":{"begin":34,"end":47}},"remediation_points":330000,"other_locations":[{"path":"lib/banzai/filter/project_reference_filter.rb","lines":{"begin":30,"end":43}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 34**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e03c415d22f94144c8a11a51bf75449a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/banzai/filter/project_reference_filter.rb","lines":{"begin":30,"end":43}},"remediation_points":330000,"other_locations":[{"path":"lib/banzai/filter/user_reference_filter.rb","lines":{"begin":34,"end":47}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 34**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8a3a69b8619d932aa827e537f3525e47","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/cycle_analytics/code_stage.rb","lines":{"begin":2,"end":25}},"remediation_points":330000,"other_locations":[{"path":"lib/gitlab/cycle_analytics/review_stage.rb","lines":{"begin":2,"end":25}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 34**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9c27f17bb5883c88a2dcf096a1e3eb10","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/cycle_analytics/review_stage.rb","lines":{"begin":2,"end":25}},"remediation_points":330000,"other_locations":[{"path":"lib/gitlab/cycle_analytics/code_stage.rb","lines":{"begin":2,"end":25}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 34**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1a85a78111fd009c906af5e3e2e5c1b3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/project_services/teamcity_service.rb","lines":{"begin":52,"end":61}},"remediation_points":310000,"other_locations":[{"path":"app/models/project_services/bamboo_service.rb","lines":{"begin":49,"end":58}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 33**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"043f75067c16ff3e5d772bc2d1ae6742","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/project_services/bamboo_service.rb","lines":{"begin":49,"end":58}},"remediation_points":310000,"other_locations":[{"path":"app/models/project_services/teamcity_service.rb","lines":{"begin":52,"end":61}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 33**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3bc437123bd781d63ac2e5f1e2ee6eb3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/background_migration/set_confidential_note_events_on_webhooks.rb","lines":{"begin":5,"end":22}},"remediation_points":310000,"other_locations":[{"path":"lib/gitlab/background_migration/set_confidential_note_events_on_services.rb","lines":{"begin":5,"end":22}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 33**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3ed85e9f30996e6d2ccc2e0700c1902e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/background_migration/set_confidential_note_events_on_services.rb","lines":{"begin":5,"end":22}},"remediation_points":310000,"other_locations":[{"path":"lib/gitlab/background_migration/set_confidential_note_events_on_webhooks.rb","lines":{"begin":5,"end":22}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 33**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7ea2c375fc2078d33f6958933228ed7c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/backup/database.rb","lines":{"begin":23,"end":27}},"remediation_points":290000,"other_locations":[{"path":"lib/backup/database.rb","lines":{"begin":57,"end":61}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 32**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"eb17727d02a7c074c3154590d6ac3890","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/backup/database.rb","lines":{"begin":57,"end":61}},"remediation_points":290000,"other_locations":[{"path":"lib/backup/database.rb","lines":{"begin":23,"end":27}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 32**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"eb17727d02a7c074c3154590d6ac3890","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/banzai/filter/user_reference_filter.rb","lines":{"begin":130,"end":135}},"remediation_points":290000,"other_locations":[{"path":"lib/banzai/filter/user_reference_filter.rb","lines":{"begin":138,"end":143}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 32**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c728f05ae3efb5098c029de0b3280f52","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/banzai/filter/user_reference_filter.rb","lines":{"begin":138,"end":143}},"remediation_points":290000,"other_locations":[{"path":"lib/banzai/filter/user_reference_filter.rb","lines":{"begin":130,"end":135}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 32**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c728f05ae3efb5098c029de0b3280f52","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/concerns/updated_at_filterable.rb","lines":{"begin":6,"end":11}},"remediation_points":270000,"other_locations":[{"path":"app/models/concerns/created_at_filterable.rb","lines":{"begin":6,"end":11}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"cf343522a426e7b5b70965cafd123e2d","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/concerns/created_at_filterable.rb","lines":{"begin":6,"end":11}},"remediation_points":270000,"other_locations":[{"path":"app/models/concerns/updated_at_filterable.rb","lines":{"begin":6,"end":11}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"83ce9e73df4d8e3e8128ba7268e47c86","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/user.rb","lines":{"begin":1183,"end":1185}},"remediation_points":270000,"other_locations":[{"path":"app/models/user.rb","lines":{"begin":1189,"end":1191}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5c5665e3faea9b14f82119a1c414984d","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/user.rb","lines":{"begin":1189,"end":1191}},"remediation_points":270000,"other_locations":[{"path":"app/models/user.rb","lines":{"begin":1183,"end":1185}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5c5665e3faea9b14f82119a1c414984d","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/legacy_github_import/importer.rb","lines":{"begin":90,"end":96}},"remediation_points":270000,"other_locations":[{"path":"lib/gitlab/legacy_github_import/importer.rb","lines":{"begin":105,"end":111}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"fb529d437944a41ecb389c07d4f4b95c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/legacy_github_import/importer.rb","lines":{"begin":105,"end":111}},"remediation_points":270000,"other_locations":[{"path":"lib/gitlab/legacy_github_import/importer.rb","lines":{"begin":90,"end":96}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"fb529d437944a41ecb389c07d4f4b95c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/system_check/app/tmp_writable_check.rb","lines":{"begin":2,"end":24}},"remediation_points":270000,"other_locations":[{"path":"lib/system_check/app/log_writable_check.rb","lines":{"begin":2,"end":24}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5873e0223d34261535ea9380a655f530","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/system_check/app/log_writable_check.rb","lines":{"begin":2,"end":24}},"remediation_points":270000,"other_locations":[{"path":"lib/system_check/app/tmp_writable_check.rb","lines":{"begin":2,"end":24}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 31**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f5f6516f48de36202ed1fb08b864850e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/services/search_service.rb","lines":{"begin":12,"end":20}},"remediation_points":250000,"other_locations":[{"path":"app/services/search_service.rb","lines":{"begin":26,"end":34}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 30**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"71865084b25bec6d7d5ead8d752fb091","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/services/search_service.rb","lines":{"begin":26,"end":34}},"remediation_points":250000,"other_locations":[{"path":"app/services/search_service.rb","lines":{"begin":12,"end":20}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 30**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"71865084b25bec6d7d5ead8d752fb091","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":192,"end":201}},"remediation_points":250000,"other_locations":[{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":205,"end":214}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 30**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"87efcabb3c142197252756a6564676fa","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":205,"end":214}},"remediation_points":250000,"other_locations":[{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":192,"end":201}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 30**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"87efcabb3c142197252756a6564676fa","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb","lines":{"begin":3,"end":12}},"remediation_points":250000,"other_locations":[{"path":"qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb","lines":{"begin":5,"end":14}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 30**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0022a6eabe051cdbca1e39ad3d0616a0","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb","lines":{"begin":5,"end":14}},"remediation_points":250000,"other_locations":[{"path":"qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb","lines":{"begin":3,"end":12}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 30**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b769bd2ad86996248bc0cb9e9909ad48","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/controllers/admin/impersonation_tokens_controller.rb","lines":{"begin":21,"end":30}},"remediation_points":230000,"other_locations":[{"path":"app/controllers/profiles/personal_access_tokens_controller.rb","lines":{"begin":19,"end":28}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d66178654f9f4136765d0ea5d8b3e694","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/controllers/profiles/personal_access_tokens_controller.rb","lines":{"begin":19,"end":28}},"remediation_points":230000,"other_locations":[{"path":"app/controllers/admin/impersonation_tokens_controller.rb","lines":{"begin":21,"end":30}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7450a990959a14aea730f2b6be2c03b2","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/controllers/projects/clusters_controller.rb","lines":{"begin":72,"end":85}},"remediation_points":230000,"other_locations":[{"path":"app/controllers/projects/clusters_controller.rb","lines":{"begin":88,"end":101}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9983d989b521b3d7814b329e7276929a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/controllers/projects/clusters_controller.rb","lines":{"begin":88,"end":101}},"remediation_points":230000,"other_locations":[{"path":"app/controllers/projects/clusters_controller.rb","lines":{"begin":72,"end":85}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9983d989b521b3d7814b329e7276929a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/blob_viewer/podspec.rb","lines":{"begin":4,"end":26}},"remediation_points":230000,"other_locations":[{"path":"app/models/blob_viewer/gemspec.rb","lines":{"begin":4,"end":26}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"bff03fdced6cbae88ff2946f20dc10e3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/blob_viewer/gemspec.rb","lines":{"begin":4,"end":26}},"remediation_points":230000,"other_locations":[{"path":"app/models/blob_viewer/podspec.rb","lines":{"begin":4,"end":26}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"51fdccc6768efcdd38f19730ffe72a7c","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/ci/stage.rb","lines":{"begin":96,"end":103}},"remediation_points":230000,"other_locations":[{"path":"app/models/ci/pipeline.rb","lines":{"begin":442,"end":449}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"25c1e443e5b07fc0b45f399a084137f7","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/ci/pipeline.rb","lines":{"begin":442,"end":449}},"remediation_points":230000,"other_locations":[{"path":"app/models/ci/stage.rb","lines":{"begin":96,"end":103}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"577c618e98095b86078d36b2ac019b73","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/settings.rb","lines":{"begin":59,"end":63}},"remediation_points":230000,"other_locations":[{"path":"lib/api/tags.rb","lines":{"begin":46,"end":50}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"34d52e18accbf89e1d40ad4efe71140e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/tags.rb","lines":{"begin":46,"end":50}},"remediation_points":230000,"other_locations":[{"path":"lib/api/settings.rb","lines":{"begin":59,"end":63}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 29**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7249b946ecaa77a923de68a038829979","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/finders/group_members_finder.rb","lines":{"begin":18,"end":22}},"remediation_points":210000,"other_locations":[{"path":"app/finders/group_members_finder.rb","lines":{"begin":26,"end":30}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b855af99fe41d98222cc7d488606c72e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/finders/group_members_finder.rb","lines":{"begin":26,"end":30}},"remediation_points":210000,"other_locations":[{"path":"app/finders/group_members_finder.rb","lines":{"begin":18,"end":22}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b855af99fe41d98222cc7d488606c72e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/members.rb","lines":{"begin":132,"end":137}},"remediation_points":210000,"other_locations":[{"path":"lib/api/access_requests.rb","lines":{"begin":76,"end":81}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"74eeab655880b068d73bfa1508e15e08","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/access_requests.rb","lines":{"begin":76,"end":81}},"remediation_points":210000,"other_locations":[{"path":"lib/api/members.rb","lines":{"begin":132,"end":137}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"89dd703acaf685f87aef279f54d3c31a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/database.rb","lines":{"begin":98,"end":109}},"remediation_points":210000,"other_locations":[{"path":"lib/gitlab/database.rb","lines":{"begin":112,"end":123}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2b8f3ea2a51c54532c43041c348ab5c3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/database.rb","lines":{"begin":112,"end":123}},"remediation_points":210000,"other_locations":[{"path":"lib/gitlab/database.rb","lines":{"begin":98,"end":109}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2b8f3ea2a51c54532c43041c348ab5c3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":44,"end":47}},"remediation_points":210000,"other_locations":[{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":50,"end":53}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e02e496b4b6107134a36720421ac55a9","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":50,"end":53}},"remediation_points":210000,"other_locations":[{"path":"lib/gitlab/gitaly_client/ref_service.rb","lines":{"begin":44,"end":47}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e02e496b4b6107134a36720421ac55a9","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"qa/spec/runtime/env_spec.rb","lines":{"begin":6,"end":14}},"remediation_points":210000,"other_locations":[{"path":"qa/spec/runtime/env_spec.rb","lines":{"begin":17,"end":25}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d4456b6f43cef6bfbf9b6d039f749cdc","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"qa/spec/runtime/env_spec.rb","lines":{"begin":17,"end":25}},"remediation_points":210000,"other_locations":[{"path":"qa/spec/runtime/env_spec.rb","lines":{"begin":6,"end":14}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 28**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d4456b6f43cef6bfbf9b6d039f749cdc","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/finders/issuable_finder.rb","lines":{"begin":252,"end":262}},"remediation_points":190000,"other_locations":[{"path":"app/finders/issuable_finder.rb","lines":{"begin":280,"end":290}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d67c2bf9c225577fade90666e5dfc6e3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/finders/issuable_finder.rb","lines":{"begin":280,"end":290}},"remediation_points":190000,"other_locations":[{"path":"app/finders/issuable_finder.rb","lines":{"begin":252,"end":262}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d67c2bf9c225577fade90666e5dfc6e3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/finders/issuable_finder.rb","lines":{"begin":396,"end":405}},"remediation_points":190000,"other_locations":[{"path":"app/finders/issuable_finder.rb","lines":{"begin":410,"end":419}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d67c2bf9c225577fade90666e5dfc6e3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/finders/issuable_finder.rb","lines":{"begin":410,"end":419}},"remediation_points":190000,"other_locations":[{"path":"app/finders/issuable_finder.rb","lines":{"begin":396,"end":405}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d67c2bf9c225577fade90666e5dfc6e3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/background_migration/migrate_legacy_artifacts.rb","lines":{"begin":24,"end":61}},"remediation_points":190000,"other_locations":[{"path":"lib/gitlab/background_migration/migrate_legacy_artifacts.rb","lines":{"begin":66,"end":104}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f75f6fbbc7064f4fdca08c087adf39c3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/background_migration/migrate_legacy_artifacts.rb","lines":{"begin":66,"end":104}},"remediation_points":190000,"other_locations":[{"path":"lib/gitlab/background_migration/migrate_legacy_artifacts.rb","lines":{"begin":24,"end":61}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f75f6fbbc7064f4fdca08c087adf39c3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/checks/change_access.rb","lines":{"begin":4,"end":17}},"remediation_points":190000,"other_locations":[{"path":"lib/gitlab/git_access.rb","lines":{"begin":12,"end":25}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ef81133143dac9759b71c4ff2fbf45ed","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/git_access.rb","lines":{"begin":12,"end":25}},"remediation_points":190000,"other_locations":[{"path":"lib/gitlab/checks/change_access.rb","lines":{"begin":4,"end":17}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"26fd0da9b8c1ef08c93581298c4ae63a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/git/repository.rb","lines":{"begin":564,"end":575}},"remediation_points":190000,"other_locations":[{"path":"lib/gitlab/git/repository.rb","lines":{"begin":579,"end":590}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7853b1dd44bd6645b11dfd03b93082d6","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/git/repository.rb","lines":{"begin":579,"end":590}},"remediation_points":190000,"other_locations":[{"path":"lib/gitlab/git/repository.rb","lines":{"begin":564,"end":575}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7853b1dd44bd6645b11dfd03b93082d6","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":211,"end":225}},"remediation_points":190000,"other_locations":[{"path":"lib/gitlab/fogbugz_import/importer.rb","lines":{"begin":204,"end":210}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"85278ceed072f59d95f4d59bb38349cd","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/fogbugz_import/importer.rb","lines":{"begin":204,"end":210}},"remediation_points":190000,"other_locations":[{"path":"lib/gitlab/google_code_import/importer.rb","lines":{"begin":211,"end":225}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 27**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"84267e4195b1ef161585bb368ab56cb4","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/mailers/emails/profile.rb","lines":{"begin":13,"end":20}},"remediation_points":170000,"other_locations":[{"path":"app/mailers/emails/profile.rb","lines":{"begin":25,"end":32}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"08b3c569353eb654d48c0f3d5dfd5507","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/mailers/emails/profile.rb","lines":{"begin":25,"end":32}},"remediation_points":170000,"other_locations":[{"path":"app/mailers/emails/profile.rb","lines":{"begin":13,"end":20}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"08b3c569353eb654d48c0f3d5dfd5507","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/prometheus_metric.rb","lines":{"begin":31,"end":43}},"remediation_points":170000,"other_locations":[{"path":"app/helpers/preferences_helper.rb","lines":{"begin":13,"end":22}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"84368938cf4e38b28be7f2494ab7dded","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/helpers/preferences_helper.rb","lines":{"begin":13,"end":22}},"remediation_points":170000,"other_locations":[{"path":"app/models/prometheus_metric.rb","lines":{"begin":31,"end":43}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"60bfb34cb2ceca3dea92da73de2720d1","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/repository.rb","lines":{"begin":826,"end":838}},"remediation_points":170000,"other_locations":[{"path":"app/models/repository.rb","lines":{"begin":842,"end":854}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f850bbad5c6c0dbf53dcda181b0fe2ef","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/repository.rb","lines":{"begin":842,"end":854}},"remediation_points":170000,"other_locations":[{"path":"app/models/repository.rb","lines":{"begin":826,"end":838}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f850bbad5c6c0dbf53dcda181b0fe2ef","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/user.rb","lines":{"begin":1195,"end":1197}},"remediation_points":170000,"other_locations":[{"path":"app/models/user.rb","lines":{"begin":1201,"end":1203}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"79536c6f949eba9317227c4e88951f1f","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/user.rb","lines":{"begin":1201,"end":1203}},"remediation_points":170000,"other_locations":[{"path":"app/models/user.rb","lines":{"begin":1195,"end":1197}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"79536c6f949eba9317227c4e88951f1f","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/presenters/project_presenter.rb","lines":{"begin":233,"end":242}},"remediation_points":170000,"other_locations":[{"path":"app/presenters/project_presenter.rb","lines":{"begin":263,"end":272}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c2d74780d6448543b93af9ac07770eb6","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/presenters/project_presenter.rb","lines":{"begin":263,"end":272}},"remediation_points":170000,"other_locations":[{"path":"app/presenters/project_presenter.rb","lines":{"begin":233,"end":242}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c2d74780d6448543b93af9ac07770eb6","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/services/protected_branches/legacy_api_update_service.rb","lines":{"begin":18,"end":22}},"remediation_points":170000,"other_locations":[{"path":"app/services/protected_branches/legacy_api_update_service.rb","lines":{"begin":25,"end":29}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2a21c2dc3f646e0e31fff25ee48b9b44","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/services/protected_branches/legacy_api_update_service.rb","lines":{"begin":25,"end":29}},"remediation_points":170000,"other_locations":[{"path":"app/services/protected_branches/legacy_api_update_service.rb","lines":{"begin":18,"end":22}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2a21c2dc3f646e0e31fff25ee48b9b44","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/services/quick_actions/interpret_service.rb","lines":{"begin":222,"end":229}},"remediation_points":170000,"other_locations":[{"path":"app/services/quick_actions/interpret_service.rb","lines":{"begin":276,"end":283}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c5f1ca2a225a96cfa3b4fc88b12b03b0","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/services/quick_actions/interpret_service.rb","lines":{"begin":276,"end":283}},"remediation_points":170000,"other_locations":[{"path":"app/services/quick_actions/interpret_service.rb","lines":{"begin":222,"end":229}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c5f1ca2a225a96cfa3b4fc88b12b03b0","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/branches.rb","lines":{"begin":71,"end":75}},"remediation_points":170000,"other_locations":[{"path":"lib/api/triggers.rb","lines":{"begin":12,"end":16}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6da3752f53f9199ec80a8ad0d2b565c1","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/triggers.rb","lines":{"begin":12,"end":16}},"remediation_points":170000,"other_locations":[{"path":"lib/api/branches.rb","lines":{"begin":71,"end":75}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2a596f82671816ebb68812ebe7bf8287","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/variables.rb","lines":{"begin":31,"end":38}},"remediation_points":170000,"other_locations":[{"path":"lib/api/group_variables.rb","lines":{"begin":31,"end":38}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9c68d19fac636a233c57119e7465aae0","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/group_variables.rb","lines":{"begin":31,"end":38}},"remediation_points":170000,"other_locations":[{"path":"lib/api/variables.rb","lines":{"begin":31,"end":38}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"fb90f649add7621cd688c8cf825bd6ac","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/banzai/filter/spaced_link_filter.rb","lines":{"begin":47,"end":56}},"remediation_points":170000,"other_locations":[{"path":"lib/banzai/filter/autolink_filter.rb","lines":{"begin":54,"end":63}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"bde5f678a848776d31606c1f7b9474fa","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/banzai/filter/autolink_filter.rb","lines":{"begin":54,"end":63}},"remediation_points":170000,"other_locations":[{"path":"lib/banzai/filter/spaced_link_filter.rb","lines":{"begin":47,"end":56}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"37daabf17a192f4d7be1f8b27c2260a6","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/background_migration/fill_file_store_job_artifact.rb","lines":{"begin":5,"end":15}},"remediation_points":170000,"other_locations":[{"path":"lib/gitlab/background_migration/fill_file_store_lfs_object.rb","lines":{"begin":5,"end":15}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8744b4f4833ad7e9ac1e28093ac9f90e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/background_migration/fill_file_store_lfs_object.rb","lines":{"begin":5,"end":15}},"remediation_points":170000,"other_locations":[{"path":"lib/gitlab/background_migration/fill_file_store_job_artifact.rb","lines":{"begin":5,"end":15}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7504dbd080f799a40cd6f4e9f9588e2e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/gitaly_client/commit_service.rb","lines":{"begin":419,"end":427}},"remediation_points":170000,"other_locations":[{"path":"lib/gitlab/gitaly_client/blob_service.rb","lines":{"begin":93,"end":101}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"63af7969c300adefbe9421c884f45ddf","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/gitaly_client/blob_service.rb","lines":{"begin":93,"end":101}},"remediation_points":170000,"other_locations":[{"path":"lib/gitlab/gitaly_client/commit_service.rb","lines":{"begin":419,"end":427}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"037891a3606e1c8a2fffd32f88e7cede","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/metrics/samplers/unicorn_sampler.rb","lines":{"begin":23,"end":25}},"remediation_points":170000,"other_locations":[{"path":"lib/gitlab/metrics/samplers/unicorn_sampler.rb","lines":{"begin":28,"end":30}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8a65a519016a876bba37ba5a3c7a819f","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/metrics/samplers/unicorn_sampler.rb","lines":{"begin":28,"end":30}},"remediation_points":170000,"other_locations":[{"path":"lib/gitlab/metrics/samplers/unicorn_sampler.rb","lines":{"begin":23,"end":25}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8a65a519016a876bba37ba5a3c7a819f","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/system_check/orphans/repository_check.rb","lines":{"begin":25,"end":32}},"remediation_points":170000,"other_locations":[{"path":"lib/system_check/orphans/namespace_check.rb","lines":{"begin":23,"end":30}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6d29fff75b609f46ded82f4a44c12b3e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/system_check/orphans/namespace_check.rb","lines":{"begin":23,"end":30}},"remediation_points":170000,"other_locations":[{"path":"lib/system_check/orphans/repository_check.rb","lines":{"begin":25,"end":32}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 26**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f5b93c21edd90205b8238e3ade31ae23","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/finders/concerns/created_at_filter.rb","lines":{"begin":4,"end":8}},"remediation_points":150000,"other_locations":[{"path":"app/finders/issuable_finder.rb","lines":{"begin":315,"end":319}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"4781409b61d0fe8557e646dde62af4ee","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/finders/issuable_finder.rb","lines":{"begin":315,"end":319}},"remediation_points":150000,"other_locations":[{"path":"app/finders/concerns/created_at_filter.rb","lines":{"begin":4,"end":8}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"99f151d4d58ba006fbb17af802305432","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/ci/pipeline.rb","lines":{"begin":572,"end":575}},"remediation_points":150000,"other_locations":[{"path":"app/models/ci/runner.rb","lines":{"begin":220,"end":223}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3c0fb0db7a1fa6852c67bd50857aa448","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/models/ci/runner.rb","lines":{"begin":220,"end":223}},"remediation_points":150000,"other_locations":[{"path":"app/models/ci/pipeline.rb","lines":{"begin":572,"end":575}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"44cfa492a10b5b72b394efe01cd9e29d","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/variables.rb","lines":{"begin":49,"end":57}},"remediation_points":150000,"other_locations":[{"path":"lib/api/group_variables.rb","lines":{"begin":49,"end":57}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"320c3b54951e69257faaca3316f72058","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/api/group_variables.rb","lines":{"begin":49,"end":57}},"remediation_points":150000,"other_locations":[{"path":"lib/api/variables.rb","lines":{"begin":49,"end":57}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"fa689b5b4bab39de500b86b3d6814963","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/background_migration/fix_cross_project_label_links.rb","lines":{"begin":65,"end":75}},"remediation_points":150000,"other_locations":[{"path":"lib/gitlab/background_migration/fix_cross_project_label_links.rb","lines":{"begin":81,"end":91}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"26a19237576df2b97ff121c3b0cd2342","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/background_migration/fix_cross_project_label_links.rb","lines":{"begin":81,"end":91}},"remediation_points":150000,"other_locations":[{"path":"lib/gitlab/background_migration/fix_cross_project_label_links.rb","lines":{"begin":65,"end":75}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"26a19237576df2b97ff121c3b0cd2342","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/gitaly_client/repository_service.rb","lines":{"begin":136,"end":150}},"remediation_points":150000,"other_locations":[{"path":"lib/gitlab/gitaly_client/repository_service.rb","lines":{"begin":153,"end":167}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"fc526fdd6733948898ee897d1eef487e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/gitaly_client/repository_service.rb","lines":{"begin":153,"end":167}},"remediation_points":150000,"other_locations":[{"path":"lib/gitlab/gitaly_client/repository_service.rb","lines":{"begin":136,"end":150}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"fc526fdd6733948898ee897d1eef487e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/redis/shared_state.rb","lines":{"begin":13,"end":29}},"remediation_points":150000,"other_locations":[{"path":"lib/gitlab/redis/queues.rb","lines":{"begin":12,"end":28}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"077a90f16d3842a49d8095f339e2187f","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/redis/queues.rb","lines":{"begin":12,"end":28}},"remediation_points":150000,"other_locations":[{"path":"lib/gitlab/redis/shared_state.rb","lines":{"begin":13,"end":29}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f48114ce0a7710485f176b1b75c08e79","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/template/issue_template.rb","lines":{"begin":2,"end":14}},"remediation_points":150000,"other_locations":[{"path":"lib/gitlab/template/merge_request_template.rb","lines":{"begin":2,"end":14}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"685a5435dac10b7fc45977c34f30aba3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"lib/gitlab/template/merge_request_template.rb","lines":{"begin":2,"end":14}},"remediation_points":150000,"other_locations":[{"path":"lib/gitlab/template/issue_template.rb","lines":{"begin":2,"end":14}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 25**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"bc65c6ec010926daa88fbdcb539ddeea","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/environments/mixins/environments_mixin.js","lines":{"begin":164,"end":170}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/jobs/job_details_mediator.js","lines":{"begin":41,"end":47}},{"path":"app/assets/javascripts/pipelines/mixins/pipelines.js","lines":{"begin":45,"end":51}},{"path":"app/assets/javascripts/pipelines/pipeline_details_mediator.js","lines":{"begin":33,"end":39}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2d6588749bb4b05d1e583e13b9a75e39","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pipelines/pipeline_details_mediator.js","lines":{"begin":33,"end":39}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/environments/mixins/environments_mixin.js","lines":{"begin":164,"end":170}},{"path":"app/assets/javascripts/jobs/job_details_mediator.js","lines":{"begin":41,"end":47}},{"path":"app/assets/javascripts/pipelines/mixins/pipelines.js","lines":{"begin":45,"end":51}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e1b42460d85ed0074c2f1fbd38bf2631","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pipelines/mixins/pipelines.js","lines":{"begin":45,"end":51}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/environments/mixins/environments_mixin.js","lines":{"begin":164,"end":170}},{"path":"app/assets/javascripts/jobs/job_details_mediator.js","lines":{"begin":41,"end":47}},{"path":"app/assets/javascripts/pipelines/pipeline_details_mediator.js","lines":{"begin":33,"end":39}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f4d765094c9218d5390de58501e17b93","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/job_details_mediator.js","lines":{"begin":41,"end":47}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/environments/mixins/environments_mixin.js","lines":{"begin":164,"end":170}},{"path":"app/assets/javascripts/pipelines/mixins/pipelines.js","lines":{"begin":45,"end":51}},{"path":"app/assets/javascripts/pipelines/pipeline_details_mediator.js","lines":{"begin":33,"end":39}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"caa789e408f667bb83169e9a6e4f54bb","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations.js","lines":{"begin":198,"end":200}},"remediation_points":690000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":142,"end":144}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":148,"end":150}},{"path":"app/assets/javascripts/ide/stores/mutations/tree.js","lines":{"begin":42,"end":44}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 58**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7b7e24c67302a04433b92238befc3cf1","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/tree.js","lines":{"begin":42,"end":44}},"remediation_points":690000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations.js","lines":{"begin":198,"end":200}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":142,"end":144}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":148,"end":150}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 58**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"935ca74221c521771d96150adf2278fe","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":142,"end":144}},"remediation_points":690000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations.js","lines":{"begin":198,"end":200}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":148,"end":150}},{"path":"app/assets/javascripts/ide/stores/mutations/tree.js","lines":{"begin":42,"end":44}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 58**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"db86386cff8c19e24cbadfb75750f1d9","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":148,"end":150}},"remediation_points":690000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations.js","lines":{"begin":198,"end":200}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":142,"end":144}},{"path":"app/assets/javascripts/ide/stores/mutations/tree.js","lines":{"begin":42,"end":44}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 58**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"db86386cff8c19e24cbadfb75750f1d9","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":7,"end":41}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":43,"end":74}},{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":76,"end":108}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"37429fdf41dd049c7cec6890f8e50c82","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":43,"end":74}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":7,"end":41}},{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":76,"end":108}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"37429fdf41dd049c7cec6890f8e50c82","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":76,"end":108}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":7,"end":41}},{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":43,"end":74}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"37429fdf41dd049c7cec6890f8e50c82","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":79,"end":96}},"remediation_points":4710000,"other_locations":[{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":97,"end":114}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 192**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"860f8a405f1b26af1f14049631bd7aa9","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":97,"end":114}},"remediation_points":4710000,"other_locations":[{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":79,"end":96}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 192**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"860f8a405f1b26af1f14049631bd7aa9","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/subscription_select.js","lines":{"begin":3,"end":26}},"remediation_points":4710000,"other_locations":[{"path":"app/assets/javascripts/issue_status_select.js","lines":{"begin":3,"end":25}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 192**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d440b0e1b0cca3d485fda9e0c4aaee09","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/issue_status_select.js","lines":{"begin":3,"end":25}},"remediation_points":4710000,"other_locations":[{"path":"app/assets/javascripts/subscription_select.js","lines":{"begin":3,"end":26}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 192**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d85cea2030ab41431ddc90787eb11389","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js","lines":{"begin":5,"end":32}},"remediation_points":4620000,"other_locations":[{"path":"app/assets/javascripts/blob/template_selectors/dockerfile_selector.js","lines":{"begin":5,"end":32}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 189**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"4a9d91d82457a2a845b5366d5f0ce5b4","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/blob/template_selectors/dockerfile_selector.js","lines":{"begin":5,"end":32}},"remediation_points":4620000,"other_locations":[{"path":"app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js","lines":{"begin":5,"end":32}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 189**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9a6d28971082883e9748dd3bac2a6695","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":316,"end":324}},"remediation_points":1470000,"other_locations":[{"path":"app/assets/javascripts/commit/pipelines/pipelines_bundle.js","lines":{"begin":34,"end":42}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 84**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5595f472c12029fb0da2e625d62d94d6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/commit/pipelines/pipelines_bundle.js","lines":{"begin":34,"end":42}},"remediation_points":1470000,"other_locations":[{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":316,"end":324}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 84**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"30d9e8739fb74a1a2a7618b85b5e6f7a","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1204,"end":1222}},"remediation_points":3990000,"other_locations":[{"path":"app/assets/javascripts/notes.js","lines":{"begin":1222,"end":1240}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 168**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"4725f16bae793b141b64c0921a6539b7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1222,"end":1240}},"remediation_points":3990000,"other_locations":[{"path":"app/assets/javascripts/notes.js","lines":{"begin":1204,"end":1222}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 168**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"4725f16bae793b141b64c0921a6539b7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":13,"end":37}},"remediation_points":2280000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":49,"end":70}},{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":82,"end":104}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 111**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"bc78d02c62558d8dc38d2567eaf302a7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":49,"end":70}},"remediation_points":2280000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":13,"end":37}},{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":82,"end":104}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 111**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"bc78d02c62558d8dc38d2567eaf302a7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":82,"end":104}},"remediation_points":2280000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":13,"end":37}},{"path":"app/assets/javascripts/ide/stores/actions/merge_request.js","lines":{"begin":49,"end":70}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 111**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"bc78d02c62558d8dc38d2567eaf302a7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/group_avatar.js","lines":{"begin":3,"end":14}},"remediation_points":3810000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/shared/project_avatar.js","lines":{"begin":3,"end":15}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 162**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"90682f4af3abe93c6405cd6eb215e9c6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/shared/project_avatar.js","lines":{"begin":3,"end":15}},"remediation_points":3810000,"other_locations":[{"path":"app/assets/javascripts/group_avatar.js","lines":{"begin":3,"end":14}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 162**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"67e63301cd433971695d7cc16ff752b2","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/components/new_list_dropdown.js","lines":{"begin":14,"end":23}},"remediation_points":1350000,"other_locations":[{"path":"app/assets/javascripts/boards/components/new_list_dropdown.js","lines":{"begin":66,"end":75}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 80**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"272dee8d71a4ab9c8d815e23c04c7742","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/components/new_list_dropdown.js","lines":{"begin":66,"end":75}},"remediation_points":1350000,"other_locations":[{"path":"app/assets/javascripts/boards/components/new_list_dropdown.js","lines":{"begin":14,"end":23}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 80**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"272dee8d71a4ab9c8d815e23c04c7742","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 5 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":76,"end":80}},"remediation_points":780000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":91,"end":95}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":96,"end":100}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":123,"end":127}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":217,"end":221}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 61**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b1e5c7e2317fc81ce11cbdb1bfee15df","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 5 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":91,"end":95}},"remediation_points":780000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":76,"end":80}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":96,"end":100}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":123,"end":127}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":217,"end":221}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 61**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b1e5c7e2317fc81ce11cbdb1bfee15df","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 5 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":96,"end":100}},"remediation_points":780000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":76,"end":80}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":91,"end":95}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":123,"end":127}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":217,"end":221}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 61**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b1e5c7e2317fc81ce11cbdb1bfee15df","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 5 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":123,"end":127}},"remediation_points":780000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":76,"end":80}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":91,"end":95}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":96,"end":100}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":217,"end":221}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 61**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b1e5c7e2317fc81ce11cbdb1bfee15df","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 5 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":217,"end":221}},"remediation_points":780000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":76,"end":80}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":91,"end":95}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":96,"end":100}},{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":123,"end":127}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 61**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b1e5c7e2317fc81ce11cbdb1bfee15df","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/u2f/register.js","lines":{"begin":55,"end":59}},"remediation_points":1050000,"other_locations":[{"path":"app/assets/javascripts/u2f/authenticate.js","lines":{"begin":70,"end":74}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 70**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2f1257b0729020dbf8c1e761c6b423db","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/u2f/authenticate.js","lines":{"begin":70,"end":74}},"remediation_points":1050000,"other_locations":[{"path":"app/assets/javascripts/u2f/register.js","lines":{"begin":55,"end":59}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 70**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"39bdab25a792c5662feb840979099a97","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/commons/polyfills/element.js","lines":{"begin":14,"end":19}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/lib/utils/common_utils.js","lines":{"begin":278,"end":283}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b95252006271b6a23d2d59296a0e5778","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/lib/utils/common_utils.js","lines":{"begin":278,"end":283}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/commons/polyfills/element.js","lines":{"begin":14,"end":19}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"67ee0da8e9d2d3b183449d9e8d462f44","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":37,"end":57}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":99,"end":135}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6e9b85292de8771601751d3ed0d9dc18","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":99,"end":135}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":37,"end":57}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6e9b85292de8771601751d3ed0d9dc18","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes/stores/actions.js","lines":{"begin":105,"end":115}},"remediation_points":2700000,"other_locations":[{"path":"app/assets/javascripts/notes/stores/actions.js","lines":{"begin":117,"end":127}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 125**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0fee1b42ff17f6df3a6f14e35f13cf18","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes/stores/actions.js","lines":{"begin":117,"end":127}},"remediation_points":2700000,"other_locations":[{"path":"app/assets/javascripts/notes/stores/actions.js","lines":{"begin":105,"end":115}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 125**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0fee1b42ff17f6df3a6f14e35f13cf18","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":183,"end":186}},"remediation_points":750000,"other_locations":[{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":188,"end":191}},{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":193,"end":196}},{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":198,"end":201}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 60**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8fc9b42e2195587e4e8d777e75ad89d8","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":188,"end":191}},"remediation_points":750000,"other_locations":[{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":183,"end":186}},{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":193,"end":196}},{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":198,"end":201}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 60**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8fc9b42e2195587e4e8d777e75ad89d8","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":193,"end":196}},"remediation_points":750000,"other_locations":[{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":183,"end":186}},{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":188,"end":191}},{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":198,"end":201}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 60**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8fc9b42e2195587e4e8d777e75ad89d8","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 4 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":198,"end":201}},"remediation_points":750000,"other_locations":[{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":183,"end":186}},{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":188,"end":191}},{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":193,"end":196}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 60**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8fc9b42e2195587e4e8d777e75ad89d8","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/snippets/show/index.js","lines":{"begin":7,"end":13}},"remediation_points":720000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/snippets/show/index.js","lines":{"begin":7,"end":13}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 59**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"15c8237c5ef3dd70061ce1d44bd5d199","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/snippets/show/index.js","lines":{"begin":7,"end":13}},"remediation_points":720000,"other_locations":[{"path":"app/assets/javascripts/pages/snippets/show/index.js","lines":{"begin":7,"end":13}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 59**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8ae282c95da75609356442ee0da6e6fc","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/groups/new_group_child.js","lines":{"begin":28,"end":36}},"remediation_points":660000,"other_locations":[{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":253,"end":262}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 57**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"611a498e9c5e5d8f256a05631ef5ac51","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":253,"end":262}},"remediation_points":660000,"other_locations":[{"path":"app/assets/javascripts/groups/new_group_child.js","lines":{"begin":28,"end":36}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 57**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"37087f35974839e5e79033d561c840a6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/issuable_bulk_update_actions.js","lines":{"begin":110,"end":117}},"remediation_points":2310000,"other_locations":[{"path":"app/assets/javascripts/issuable_bulk_update_actions.js","lines":{"begin":120,"end":126}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 112**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0e6b703c746c94d22e3a02f1993665ce","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/issuable_bulk_update_actions.js","lines":{"begin":120,"end":126}},"remediation_points":2310000,"other_locations":[{"path":"app/assets/javascripts/issuable_bulk_update_actions.js","lines":{"begin":110,"end":117}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 112**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0e6b703c746c94d22e3a02f1993665ce","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"scripts/frontend/prettier.js","lines":{"begin":64,"end":64}},"remediation_points":600000,"other_locations":[{"path":"scripts/frontend/prettier.js","lines":{"begin":71,"end":71}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 55**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ef21ab3df8b01ca7e22d4d8804157d7a","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"scripts/frontend/prettier.js","lines":{"begin":71,"end":71}},"remediation_points":600000,"other_locations":[{"path":"scripts/frontend/prettier.js","lines":{"begin":64,"end":64}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 55**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ef21ab3df8b01ca7e22d4d8804157d7a","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/api.js","lines":{"begin":208,"end":211}},"remediation_points":1140000,"other_locations":[{"path":"app/assets/javascripts/api.js","lines":{"begin":213,"end":216}},{"path":"app/assets/javascripts/api.js","lines":{"begin":218,"end":221}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 73**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8774a69c35e7469e5831f013a372ab41","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/api.js","lines":{"begin":213,"end":216}},"remediation_points":1140000,"other_locations":[{"path":"app/assets/javascripts/api.js","lines":{"begin":208,"end":211}},{"path":"app/assets/javascripts/api.js","lines":{"begin":218,"end":221}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 73**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8774a69c35e7469e5831f013a372ab41","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/api.js","lines":{"begin":218,"end":221}},"remediation_points":1140000,"other_locations":[{"path":"app/assets/javascripts/api.js","lines":{"begin":208,"end":211}},{"path":"app/assets/javascripts/api.js","lines":{"begin":213,"end":216}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 73**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8774a69c35e7469e5831f013a372ab41","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pipelines/mixins/pipelines.js","lines":{"begin":58,"end":63}},"remediation_points":1110000,"other_locations":[{"path":"app/assets/javascripts/boards/components/board_sidebar.js","lines":{"begin":95,"end":100}},{"path":"app/assets/javascripts/boards/index.js","lines":{"begin":84,"end":89}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 72**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6b694c569da1307b1f359dff7c2ba96a","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/components/board_sidebar.js","lines":{"begin":95,"end":100}},"remediation_points":1110000,"other_locations":[{"path":"app/assets/javascripts/boards/index.js","lines":{"begin":84,"end":89}},{"path":"app/assets/javascripts/pipelines/mixins/pipelines.js","lines":{"begin":58,"end":63}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 72**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3334e14e9af8a36a250b2eeea4a85929","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/index.js","lines":{"begin":84,"end":89}},"remediation_points":1110000,"other_locations":[{"path":"app/assets/javascripts/boards/components/board_sidebar.js","lines":{"begin":95,"end":100}},{"path":"app/assets/javascripts/pipelines/mixins/pipelines.js","lines":{"begin":58,"end":63}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 72**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7f09eaadb7107879ce06fbd895c6dfd2","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/issue.js","lines":{"begin":133,"end":142}},"remediation_points":2130000,"other_locations":[{"path":"app/assets/javascripts/issue.js","lines":{"begin":144,"end":153}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 106**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"316f57fc6559be3f06b67e902431d4f3","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/issue.js","lines":{"begin":144,"end":153}},"remediation_points":2130000,"other_locations":[{"path":"app/assets/javascripts/issue.js","lines":{"begin":133,"end":142}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 106**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"316f57fc6559be3f06b67e902431d4f3","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js","lines":{"begin":10,"end":15}},"remediation_points":480000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js","lines":{"begin":19,"end":24}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 51**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7d9d7ed069d90637ae3219e74dfc3a6a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js","lines":{"begin":19,"end":24}},"remediation_points":480000,"other_locations":[{"path":"app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js","lines":{"begin":10,"end":15}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 51**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"de8d186c2d499f4864bd5be7db1ddad4","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/u2f/register.js","lines":{"begin":44,"end":53}},"remediation_points":2010000,"other_locations":[{"path":"app/assets/javascripts/u2f/authenticate.js","lines":{"begin":59,"end":68}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 102**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c1e5733385780c688efc3db1ad11f746","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/u2f/authenticate.js","lines":{"begin":59,"end":68}},"remediation_points":2010000,"other_locations":[{"path":"app/assets/javascripts/u2f/register.js","lines":{"begin":44,"end":53}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 102**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8de13799b826a6e485d4019569e6c278","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":112,"end":122}},"remediation_points":1980000,"other_locations":[{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":124,"end":134}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 101**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"762a1afa922792a6981db079fca70fa7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":124,"end":134}},"remediation_points":1980000,"other_locations":[{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":112,"end":122}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 101**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"762a1afa922792a6981db079fca70fa7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/projects_list.js","lines":{"begin":7,"end":18}},"remediation_points":1980000,"other_locations":[{"path":"app/assets/javascripts/groups_list.js","lines":{"begin":7,"end":18}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 101**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"23431c5334e56a528fc4eb179e3607ec","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/groups_list.js","lines":{"begin":7,"end":18}},"remediation_points":1980000,"other_locations":[{"path":"app/assets/javascripts/projects_list.js","lines":{"begin":7,"end":18}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 101**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1c7090c499a2a96d464475ad7f818f11","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/users_select.js","lines":{"begin":605,"end":609}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/users_select.js","lines":{"begin":610,"end":614}},{"path":"app/assets/javascripts/users_select.js","lines":{"begin":615,"end":619}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"448aadd60c1a014f7e3ec3eed8404a65","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/users_select.js","lines":{"begin":610,"end":614}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/users_select.js","lines":{"begin":605,"end":609}},{"path":"app/assets/javascripts/users_select.js","lines":{"begin":615,"end":619}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"448aadd60c1a014f7e3ec3eed8404a65","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/users_select.js","lines":{"begin":615,"end":619}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/users_select.js","lines":{"begin":605,"end":609}},{"path":"app/assets/javascripts/users_select.js","lines":{"begin":610,"end":614}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"448aadd60c1a014f7e3ec3eed8404a65","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/droplab/plugins/filter.js","lines":{"begin":84,"end":90}},"remediation_points":1890000,"other_locations":[{"path":"app/assets/javascripts/droplab/plugins/ajax_filter.js","lines":{"begin":102,"end":108}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 98**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"38d9afeb8b320571be87977725fdcafe","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/droplab/plugins/ajax_filter.js","lines":{"begin":102,"end":108}},"remediation_points":1890000,"other_locations":[{"path":"app/assets/javascripts/droplab/plugins/filter.js","lines":{"begin":84,"end":90}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 98**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8f8bc2a8aade90176f81a992ae00dd6e","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js","lines":{"begin":1,"end":21}},"remediation_points":1860000,"other_locations":[{"path":"app/assets/javascripts/badges/store/mutation_types.js","lines":{"begin":1,"end":21}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 97**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7a9bae0109305e0b1d43de56513651bb","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/badges/store/mutation_types.js","lines":{"begin":1,"end":21}},"remediation_points":1860000,"other_locations":[{"path":"app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js","lines":{"begin":1,"end":21}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 97**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"500922ffefee136d140bb5e95d7a3653","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/protected_tags/protected_tag_edit_list.js","lines":{"begin":6,"end":19}},"remediation_points":1830000,"other_locations":[{"path":"app/assets/javascripts/protected_branches/protected_branch_edit_list.js","lines":{"begin":6,"end":19}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 96**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"746b71495fe41db39299682caa93445c","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/protected_branches/protected_branch_edit_list.js","lines":{"begin":6,"end":19}},"remediation_points":1830000,"other_locations":[{"path":"app/assets/javascripts/protected_tags/protected_tag_edit_list.js","lines":{"begin":6,"end":19}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 96**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f5f6ce24443dd76a3e1d0f7521a36be5","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/settings_panels.js","lines":{"begin":7,"end":10}},"remediation_points":390000,"other_locations":[{"path":"app/assets/javascripts/settings_panels.js","lines":{"begin":17,"end":20}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 48**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e788e65abb5a4f6fb80e4e6628fb6d10","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/settings_panels.js","lines":{"begin":17,"end":20}},"remediation_points":390000,"other_locations":[{"path":"app/assets/javascripts/settings_panels.js","lines":{"begin":7,"end":10}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 48**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e788e65abb5a4f6fb80e4e6628fb6d10","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":40,"end":40}},"remediation_points":360000,"other_locations":[{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":47,"end":47}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 47**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3f756c21d7aebceedb361afbef2d4a17","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":47,"end":47}},"remediation_points":360000,"other_locations":[{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":40,"end":40}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 47**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3f756c21d7aebceedb361afbef2d4a17","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":32,"end":34}},"remediation_points":330000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":243,"end":245}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 46**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"50b54b3d38d73c256194eed3de425376","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"identical-code","description":"Identical blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":243,"end":245}},"remediation_points":330000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations/file.js","lines":{"begin":32,"end":34}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 46**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"50b54b3d38d73c256194eed3de425376","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_request.js","lines":{"begin":96,"end":103}},"remediation_points":1680000,"other_locations":[{"path":"app/assets/javascripts/merge_request.js","lines":{"begin":105,"end":112}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 91**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d3d4aa6b1ed1ed8586dbb1479749be92","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_request.js","lines":{"begin":105,"end":112}},"remediation_points":1680000,"other_locations":[{"path":"app/assets/javascripts/merge_request.js","lines":{"begin":96,"end":103}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 91**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d3d4aa6b1ed1ed8586dbb1479749be92","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/sidebar/mount_sidebar.js","lines":{"begin":75,"end":92}},"remediation_points":1680000,"other_locations":[{"path":"app/assets/javascripts/sidebar/mount_sidebar.js","lines":{"begin":94,"end":111}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 91**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"961bf4d63d82bd0547b50951a7db6c69","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/sidebar/mount_sidebar.js","lines":{"begin":94,"end":111}},"remediation_points":1680000,"other_locations":[{"path":"app/assets/javascripts/sidebar/mount_sidebar.js","lines":{"begin":75,"end":92}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 91**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"961bf4d63d82bd0547b50951a7db6c69","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":514,"end":533}},"remediation_points":1650000,"other_locations":[{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":522,"end":533}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 90**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e69530c757a3f18db186fc48d529606d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":522,"end":533}},"remediation_points":1650000,"other_locations":[{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":514,"end":533}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 90**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e69530c757a3f18db186fc48d529606d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/badges/store/mutations.js","lines":{"begin":85,"end":99}},"remediation_points":1620000,"other_locations":[{"path":"app/assets/javascripts/badges/store/mutations.js","lines":{"begin":100,"end":114}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 89**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0407713555d3e0c75813d9f23089bcfd","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/badges/store/mutations.js","lines":{"begin":100,"end":114}},"remediation_points":1620000,"other_locations":[{"path":"app/assets/javascripts/badges/store/mutations.js","lines":{"begin":85,"end":99}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 89**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0407713555d3e0c75813d9f23089bcfd","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/monitoring/services/monitoring_service.js","lines":{"begin":34,"end":41}},"remediation_points":1620000,"other_locations":[{"path":"app/assets/javascripts/monitoring/services/monitoring_service.js","lines":{"begin":48,"end":55}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 89**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d4a3e547360e709e6f65d844f47d2578","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/monitoring/services/monitoring_service.js","lines":{"begin":48,"end":55}},"remediation_points":1620000,"other_locations":[{"path":"app/assets/javascripts/monitoring/services/monitoring_service.js","lines":{"begin":34,"end":41}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 89**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d4a3e547360e709e6f65d844f47d2578","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/cycle_analytics/default_event_objects.js","lines":{"begin":2,"end":14}},"remediation_points":690000,"other_locations":[{"path":"app/assets/javascripts/cycle_analytics/default_event_objects.js","lines":{"begin":27,"end":39}},{"path":"app/assets/javascripts/cycle_analytics/default_event_objects.js","lines":{"begin":85,"end":97}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 58**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ac500b1ccac9075c6ed2fffbc9b3961c","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/cycle_analytics/default_event_objects.js","lines":{"begin":27,"end":39}},"remediation_points":690000,"other_locations":[{"path":"app/assets/javascripts/cycle_analytics/default_event_objects.js","lines":{"begin":2,"end":14}},{"path":"app/assets/javascripts/cycle_analytics/default_event_objects.js","lines":{"begin":85,"end":97}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 58**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ac500b1ccac9075c6ed2fffbc9b3961c","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/cycle_analytics/default_event_objects.js","lines":{"begin":85,"end":97}},"remediation_points":690000,"other_locations":[{"path":"app/assets/javascripts/cycle_analytics/default_event_objects.js","lines":{"begin":2,"end":14}},{"path":"app/assets/javascripts/cycle_analytics/default_event_objects.js","lines":{"begin":27,"end":39}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 58**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ac500b1ccac9075c6ed2fffbc9b3961c","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/groups/store/groups_store.js","lines":{"begin":46,"end":57}},"remediation_points":1530000,"other_locations":[{"path":"app/assets/javascripts/pipelines/stores/pipelines_store.js","lines":{"begin":20,"end":31}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 86**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ac70092b1281d03613501e7bff10586e","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pipelines/stores/pipelines_store.js","lines":{"begin":20,"end":31}},"remediation_points":1530000,"other_locations":[{"path":"app/assets/javascripts/groups/store/groups_store.js","lines":{"begin":46,"end":57}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 86**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d99660a2eda83bcadef8b0ab894abe34","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/badges/store/actions.js","lines":{"begin":28,"end":39}},"remediation_points":1500000,"other_locations":[{"path":"app/assets/javascripts/badges/store/actions.js","lines":{"begin":142,"end":153}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 85**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2d5915d3ead3c5d344920b58d2d29398","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/badges/store/actions.js","lines":{"begin":142,"end":153}},"remediation_points":1500000,"other_locations":[{"path":"app/assets/javascripts/badges/store/actions.js","lines":{"begin":28,"end":39}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 85**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2d5915d3ead3c5d344920b58d2d29398","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":58,"end":62}},"remediation_points":600000,"other_locations":[{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":78,"end":82}},{"path":"app/assets/javascripts/sidebar/stores/sidebar_store.js","lines":{"begin":77,"end":81}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 55**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6bca973f7e7c682a30bc1ec4bf9bd764","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":78,"end":82}},"remediation_points":600000,"other_locations":[{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":58,"end":62}},{"path":"app/assets/javascripts/sidebar/stores/sidebar_store.js","lines":{"begin":77,"end":81}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 55**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6bca973f7e7c682a30bc1ec4bf9bd764","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/sidebar/stores/sidebar_store.js","lines":{"begin":77,"end":81}},"remediation_points":600000,"other_locations":[{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":58,"end":62}},{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":78,"end":82}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 55**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0eb61104cf3e71a06f399141f9b4fa0d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":50,"end":53}},"remediation_points":600000,"other_locations":[{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":139,"end":142}},{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":162,"end":165}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 55**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ab644c43df256102910e1758c76cc0b5","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":139,"end":142}},"remediation_points":600000,"other_locations":[{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":50,"end":53}},{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":162,"end":165}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 55**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ab644c43df256102910e1758c76cc0b5","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":162,"end":165}},"remediation_points":600000,"other_locations":[{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":50,"end":53}},{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":139,"end":142}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 55**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ab644c43df256102910e1758c76cc0b5","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":22,"end":35}},"remediation_points":1410000,"other_locations":[{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":83,"end":96}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 82**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"25ff43fa592634a300a75d479e7131a7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":83,"end":96}},"remediation_points":1410000,"other_locations":[{"path":"app/assets/javascripts/gl_dropdown.js","lines":{"begin":22,"end":35}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 82**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"25ff43fa592634a300a75d479e7131a7","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diffs/store/actions.js","lines":{"begin":85,"end":91}},"remediation_points":1380000,"other_locations":[{"path":"app/assets/javascripts/diffs/store/actions.js","lines":{"begin":93,"end":99}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8761bbf2f2e7182d15c059e6eda61093","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diffs/store/actions.js","lines":{"begin":93,"end":99}},"remediation_points":1380000,"other_locations":[{"path":"app/assets/javascripts/diffs/store/actions.js","lines":{"begin":85,"end":91}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"8761bbf2f2e7182d15c059e6eda61093","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/blob/show/index.js","lines":{"begin":13,"end":29}},"remediation_points":1380000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/tree/show/index.js","lines":{"begin":24,"end":40}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"10512ee694664bf38a3b98f1983d771f","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/tree/show/index.js","lines":{"begin":24,"end":40}},"remediation_points":1380000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/blob/show/index.js","lines":{"begin":13,"end":29}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"cacb4070042747bc39f22b6ed696d288","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":45,"end":51}},"remediation_points":1380000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":59,"end":65}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"14f5cdeb3ea2bd6b45456111559d4372","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":59,"end":65}},"remediation_points":1380000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":45,"end":51}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 81**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"14f5cdeb3ea2bd6b45456111559d4372","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":150,"end":154}},"remediation_points":1350000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":156,"end":160}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 80**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2c9b1f08a21d96ed70ae569a5f20147b","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":156,"end":160}},"remediation_points":1350000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":150,"end":154}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 80**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2c9b1f08a21d96ed70ae569a5f20147b","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":377,"end":382}},"remediation_points":1320000,"other_locations":[{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":382,"end":387}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 79**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f7da63bef0b10cc1b2852a187aa6da7a","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":382,"end":387}},"remediation_points":1320000,"other_locations":[{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":377,"end":382}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 79**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f7da63bef0b10cc1b2852a187aa6da7a","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":14,"end":14}},"remediation_points":1290000,"other_locations":[{"path":"app/assets/javascripts/monitoring/utils/multiple_time_series.js","lines":{"begin":8,"end":19}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 78**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c151b6404d32d859c90476abc53a459a","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/monitoring/utils/multiple_time_series.js","lines":{"begin":8,"end":19}},"remediation_points":1290000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":14,"end":14}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 78**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2e1258cb46a94d486ff0c217b7ee506b","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/tree.js","lines":{"begin":78,"end":84}},"remediation_points":1260000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":81,"end":87}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 77**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5801c2247d4ab393293432462fbf849b","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":81,"end":87}},"remediation_points":1260000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/tree.js","lines":{"begin":78,"end":84}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 77**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0b9486994211d1c4a0385b3c9ac7339f","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/droplab/hook_input.js","lines":{"begin":4,"end":14}},"remediation_points":1230000,"other_locations":[{"path":"app/assets/javascripts/droplab/hook_button.js","lines":{"begin":4,"end":14}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 76**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ce9535c497280ac0f885dd216908adc4","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/droplab/hook_button.js","lines":{"begin":4,"end":14}},"remediation_points":1230000,"other_locations":[{"path":"app/assets/javascripts/droplab/hook_input.js","lines":{"begin":4,"end":14}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 76**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5ee1687cc7901d1085a41d0b7d5b3730","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/modules/pipelines/actions.js","lines":{"begin":84,"end":96}},"remediation_points":1230000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/file_templates/actions.js","lines":{"begin":53,"end":65}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 76**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0d9f79efa645b760bbea0b48922a8231","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/modules/file_templates/actions.js","lines":{"begin":53,"end":65}},"remediation_points":1230000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/pipelines/actions.js","lines":{"begin":84,"end":96}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 76**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9033fca3ba0465c4cc79203ac97a67c6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/behaviors/preview_markdown.js","lines":{"begin":197,"end":202}},"remediation_points":1200000,"other_locations":[{"path":"app/assets/javascripts/behaviors/preview_markdown.js","lines":{"begin":204,"end":209}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 75**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"46e2ef5ccb0e3676b035d790303474f6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/behaviors/preview_markdown.js","lines":{"begin":204,"end":209}},"remediation_points":1200000,"other_locations":[{"path":"app/assets/javascripts/behaviors/preview_markdown.js","lines":{"begin":197,"end":202}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 75**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"46e2ef5ccb0e3676b035d790303474f6","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/reports/store/actions.js","lines":{"begin":61,"end":67}},"remediation_points":450000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/pipelines/actions.js","lines":{"begin":73,"end":79}},{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":56,"end":62}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f6738e3ca61bc9141c14aca35a4ece63","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":56,"end":62}},"remediation_points":450000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/pipelines/actions.js","lines":{"begin":73,"end":79}},{"path":"app/assets/javascripts/reports/store/actions.js","lines":{"begin":61,"end":67}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b0ce2389e2ef7f95bdab6dd30392133a","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/modules/pipelines/actions.js","lines":{"begin":73,"end":79}},"remediation_points":450000,"other_locations":[{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":56,"end":62}},{"path":"app/assets/javascripts/reports/store/actions.js","lines":{"begin":61,"end":67}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e577605dea8912ffd5b82ee2e188ec76","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/modules/pipelines/actions.js","lines":{"begin":28,"end":40}},"remediation_points":1170000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/pipelines/actions.js","lines":{"begin":123,"end":133}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 74**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1825ad84d47a08c46a9dd2a4386db970","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/modules/pipelines/actions.js","lines":{"begin":123,"end":133}},"remediation_points":1170000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/pipelines/actions.js","lines":{"begin":28,"end":40}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 74**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1825ad84d47a08c46a9dd2a4386db970","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js","lines":{"begin":61,"end":67}},"remediation_points":1140000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js","lines":{"begin":68,"end":74}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 73**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"40924a71c4587ae28cb080829b65871c","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js","lines":{"begin":68,"end":74}},"remediation_points":1140000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js","lines":{"begin":61,"end":67}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 73**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"40924a71c4587ae28cb080829b65871c","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":7,"end":9}},"remediation_points":1140000,"other_locations":[{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":13,"end":15}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 73**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ca3e7297c2c42380a6b34e26f5faa025","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":13,"end":15}},"remediation_points":1140000,"other_locations":[{"path":"app/assets/javascripts/test_utils/simulate_drag.js","lines":{"begin":7,"end":9}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 73**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ca3e7297c2c42380a6b34e26f5faa025","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/merge_request.js","lines":{"begin":22,"end":26}},"remediation_points":1110000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations/branch.js","lines":{"begin":26,"end":30}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 72**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5b01e7cc1ccdb5b45e44117d01906827","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/mutations/branch.js","lines":{"begin":26,"end":30}},"remediation_points":1110000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/mutations/merge_request.js","lines":{"begin":22,"end":26}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 72**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e8265ff476562ddb4bd141410d88ad2f","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/labels/index/index.js","lines":{"begin":12,"end":18}},"remediation_points":1110000,"other_locations":[{"path":"app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js","lines":{"begin":9,"end":15}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 72**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"33bcc15ccc0be1f4e4787d8882ad36e2","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js","lines":{"begin":9,"end":15}},"remediation_points":1110000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/labels/index/index.js","lines":{"begin":12,"end":18}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 72**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"931ead2443e997ee26d698667339fb06","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":222,"end":229}},"remediation_points":1080000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":237,"end":244}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 71**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"36bae5f12de5a60023e0fd8fc19f1842","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":237,"end":244}},"remediation_points":1080000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":222,"end":229}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 71**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"36bae5f12de5a60023e0fd8fc19f1842","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/u2f/register.js","lines":{"begin":71,"end":77}},"remediation_points":1080000,"other_locations":[{"path":"app/assets/javascripts/u2f/authenticate.js","lines":{"begin":81,"end":87}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 71**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"47474e924fc42a7e0f1319c7a8ae13d3","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/u2f/authenticate.js","lines":{"begin":81,"end":87}},"remediation_points":1080000,"other_locations":[{"path":"app/assets/javascripts/u2f/register.js","lines":{"begin":71,"end":77}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 71**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"aade96d11a83331446e70683959d49ec","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":381,"end":385}},"remediation_points":1050000,"other_locations":[{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":418,"end":422}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 70**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"fdc3dab0afea85797bddfc16706a3869","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":418,"end":422}},"remediation_points":1050000,"other_locations":[{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":381,"end":385}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 70**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"fdc3dab0afea85797bddfc16706a3869","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diffs/store/getters.js","lines":{"begin":80,"end":83}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/diffs/store/getters.js","lines":{"begin":84,"end":87}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"85e1a20133858d47a7f4a64e49080aef","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diffs/store/getters.js","lines":{"begin":84,"end":87}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/diffs/store/getters.js","lines":{"begin":80,"end":83}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"85e1a20133858d47a7f4a64e49080aef","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/frequent_items/constants.js","lines":{"begin":18,"end":28}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/frequent_items/constants.js","lines":{"begin":29,"end":37}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2c7c7576a99db4f904055a712b428b46","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/frequent_items/constants.js","lines":{"begin":29,"end":37}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/frequent_items/constants.js","lines":{"begin":18,"end":28}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2c7c7576a99db4f904055a712b428b46","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/groups/index.js","lines":{"begin":79,"end":89}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/environments/folder/environments_folder_bundle.js","lines":{"begin":24,"end":34}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b80cd93182a7ed27e84c9e59840d8f01","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/environments/folder/environments_folder_bundle.js","lines":{"begin":24,"end":34}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/groups/index.js","lines":{"begin":79,"end":89}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2beaf3990d7c635f7339eb68a668c3a8","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/modules/file_templates/actions.js","lines":{"begin":9,"end":20}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/commit/actions.js","lines":{"begin":202,"end":213}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9441eb5a1202300a1a6bfdd0f0275a31","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/modules/commit/actions.js","lines":{"begin":202,"end":213}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/file_templates/actions.js","lines":{"begin":9,"end":20}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f657ac30a9ca0e466c504f33ae3363a1","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":259,"end":264}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":264,"end":269}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"40a4102731fea710642a925d4403b398","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":264,"end":269}},"remediation_points":1020000,"other_locations":[{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":259,"end":264}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 69**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"40a4102731fea710642a925d4403b398","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diff_notes/services/resolve.js","lines":{"begin":56,"end":68}},"remediation_points":990000,"other_locations":[{"path":"app/assets/javascripts/diff_notes/services/resolve.js","lines":{"begin":70,"end":82}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 68**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f5c966c3b72bce8578a3e8c47c412d39","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diff_notes/services/resolve.js","lines":{"begin":70,"end":82}},"remediation_points":990000,"other_locations":[{"path":"app/assets/javascripts/diff_notes/services/resolve.js","lines":{"begin":56,"end":68}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 68**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f5c966c3b72bce8578a3e8c47c412d39","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/modules/pipelines/mutations.js","lines":{"begin":40,"end":45}},"remediation_points":990000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/pipelines/mutations.js","lines":{"begin":46,"end":51}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 68**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5aa5f88f636432f3381edff9ce5eb33c","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/modules/pipelines/mutations.js","lines":{"begin":46,"end":51}},"remediation_points":990000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/pipelines/mutations.js","lines":{"begin":40,"end":45}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 68**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5aa5f88f636432f3381edff9ce5eb33c","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":207,"end":220}},"remediation_points":990000,"other_locations":[{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":221,"end":234}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 68**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c3008559d14ec2056a4514ffdfd5d04b","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":221,"end":234}},"remediation_points":990000,"other_locations":[{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":207,"end":220}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 68**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c3008559d14ec2056a4514ffdfd5d04b","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes/stores/utils.js","lines":{"begin":16,"end":23}},"remediation_points":990000,"other_locations":[{"path":"app/assets/javascripts/notes.js","lines":{"begin":1506,"end":1515}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 68**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"37585e488e2d24ae8b8b99880ff76099","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1506,"end":1515}},"remediation_points":990000,"other_locations":[{"path":"app/assets/javascripts/notes/stores/utils.js","lines":{"begin":16,"end":23}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 68**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ea9f3aa39779bc4f5ead96ecdcd5e87c","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1526,"end":1566}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/commit/actions.js","lines":{"begin":103,"end":219}},{"path":"app/assets/javascripts/pages/search/init_filtered_search.js","lines":{"begin":3,"end":23}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5306c1b3e6d8ac0ac9bed008b527b8a7","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/search/init_filtered_search.js","lines":{"begin":3,"end":23}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/modules/commit/actions.js","lines":{"begin":103,"end":219}},{"path":"app/assets/javascripts/notes.js","lines":{"begin":1526,"end":1566}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"4dfd0fcaf710a6337c5924d7c4fa91ea","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/modules/commit/actions.js","lines":{"begin":103,"end":219}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/notes.js","lines":{"begin":1526,"end":1566}},{"path":"app/assets/javascripts/pages/search/init_filtered_search.js","lines":{"begin":3,"end":23}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"71749d080c69b950423ce3e73b3671d6","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/labels/index/index.js","lines":{"begin":44,"end":48}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js","lines":{"begin":44,"end":48}},{"path":"app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js","lines":{"begin":39,"end":43}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"80bd962a66c5ba8f3bb261204e0e8404","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js","lines":{"begin":44,"end":48}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js","lines":{"begin":39,"end":43}},{"path":"app/assets/javascripts/pages/projects/labels/index/index.js","lines":{"begin":44,"end":48}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"4dccfbb8118720c67894021eefaeb04f","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 3 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js","lines":{"begin":39,"end":43}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js","lines":{"begin":44,"end":48}},{"path":"app/assets/javascripts/pages/projects/labels/index/index.js","lines":{"begin":44,"end":48}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"594be23bc97d64731abf2df62520ccd3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/clusters/services/clusters_service.js","lines":{"begin":6,"end":12}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/clusters/clusters_bundle.js","lines":{"begin":96,"end":102}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5b85961f2c362ae642ccf393b8352fcb","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/clusters/clusters_bundle.js","lines":{"begin":96,"end":102}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/clusters/services/clusters_service.js","lines":{"begin":6,"end":12}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"cad81c7468728ba369f9f23c8a1271bc","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":91,"end":93}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":172,"end":174}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"beda25571cf3ea070a68d9cdfae91c8d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":172,"end":174}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions/file.js","lines":{"begin":91,"end":93}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"beda25571cf3ea070a68d9cdfae91c8d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js","lines":{"begin":48,"end":53}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js","lines":{"begin":63,"end":67}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"575f1444e3c2fec5a4f47d049cbd87c0","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js","lines":{"begin":63,"end":67}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js","lines":{"begin":48,"end":53}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5e4303b5381a6be7bcaade373d766f34","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/u2f/register.js","lines":{"begin":35,"end":42}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/u2f/authenticate.js","lines":{"begin":50,"end":57}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1d8b266328571b1177e922384e474eb5","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/u2f/authenticate.js","lines":{"begin":50,"end":57}},"remediation_points":960000,"other_locations":[{"path":"app/assets/javascripts/u2f/register.js","lines":{"begin":35,"end":42}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 67**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"334a900ffecf98fb6a951456c58236a1","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":181,"end":183}},"remediation_points":930000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":290,"end":292}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 66**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"32fbc7cab878611e7f156798ae3d2bca","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":290,"end":292}},"remediation_points":930000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":181,"end":183}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 66**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"32fbc7cab878611e7f156798ae3d2bca","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/project.js","lines":{"begin":50,"end":56}},"remediation_points":930000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/project.js","lines":{"begin":57,"end":63}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 66**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2c14eefb5c2db69bb969956efd4384de","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/project.js","lines":{"begin":57,"end":63}},"remediation_points":930000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/project.js","lines":{"begin":50,"end":56}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 66**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2c14eefb5c2db69bb969956efd4384de","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":277,"end":286}},"remediation_points":930000,"other_locations":[{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":287,"end":296}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 66**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"923d82c4da880c23e929c2402aedd37e","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":287,"end":296}},"remediation_points":930000,"other_locations":[{"path":"app/assets/javascripts/search_autocomplete.js","lines":{"begin":277,"end":286}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 66**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"923d82c4da880c23e929c2402aedd37e","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/blob/blob_fork_suggestion.js","lines":{"begin":31,"end":34}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/blob/blob_fork_suggestion.js","lines":{"begin":56,"end":59}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"938856cdef6ef5aa2933db645ea96362","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/blob/blob_fork_suggestion.js","lines":{"begin":56,"end":59}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/blob/blob_fork_suggestion.js","lines":{"begin":31,"end":34}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"938856cdef6ef5aa2933db645ea96362","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diffs/index.js","lines":{"begin":30,"end":39}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/mr_notes/index.js","lines":{"begin":66,"end":75}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b35237501aeed21f2d38aef31627b97e","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/mr_notes/index.js","lines":{"begin":66,"end":75}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/diffs/index.js","lines":{"begin":30,"end":39}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"17da47f3678555baddd89ae6622e441f","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":18,"end":31}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":33,"end":46}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"a93d28498cd20f4d35a3f8dbbc0a7a35","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":33,"end":46}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/lib/utils/datetime_utility.js","lines":{"begin":18,"end":31}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"a93d28498cd20f4d35a3f8dbbc0a7a35","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":237,"end":241}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":340,"end":344}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"785078efeabfa8c98aa5bf0804b2619d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":340,"end":344}},"remediation_points":870000,"other_locations":[{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":237,"end":241}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 64**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"785078efeabfa8c98aa5bf0804b2619d","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/utils.js","lines":{"begin":112,"end":122}},"remediation_points":840000,"other_locations":[{"path":"app/assets/javascripts/ide/lib/diff/controller.js","lines":{"begin":6,"end":16}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 63**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"09d7d5a8cacd9d95c32bc8b4e6b53930","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/lib/diff/controller.js","lines":{"begin":6,"end":16}},"remediation_points":840000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/utils.js","lines":{"begin":112,"end":122}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 63**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f94a7934b1c80152deb5b61a0c5db3d4","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/api.js","lines":{"begin":118,"end":124}},"remediation_points":810000,"other_locations":[{"path":"app/assets/javascripts/api.js","lines":{"begin":126,"end":132}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 62**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"104c7ee7f9a45a32c42bacfaa5a5f1bc","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/api.js","lines":{"begin":126,"end":132}},"remediation_points":810000,"other_locations":[{"path":"app/assets/javascripts/api.js","lines":{"begin":118,"end":124}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 62**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"104c7ee7f9a45a32c42bacfaa5a5f1bc","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/comment_type_toggle.js","lines":{"begin":37,"end":46}},"remediation_points":810000,"other_locations":[{"path":"app/assets/javascripts/comment_type_toggle.js","lines":{"begin":48,"end":57}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 62**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1f17e867f83499d67ff9ed50b8097db5","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/comment_type_toggle.js","lines":{"begin":48,"end":57}},"remediation_points":810000,"other_locations":[{"path":"app/assets/javascripts/comment_type_toggle.js","lines":{"begin":37,"end":46}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 62**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1f17e867f83499d67ff9ed50b8097db5","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pipelines/pipeline_details_bundle.js","lines":{"begin":77,"end":84}},"remediation_points":810000,"other_locations":[{"path":"app/assets/javascripts/jobs/job_details_bundle.js","lines":{"begin":27,"end":34}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 62**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c4a434df3787893bc1f6d897464a8227","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/job_details_bundle.js","lines":{"begin":27,"end":34}},"remediation_points":810000,"other_locations":[{"path":"app/assets/javascripts/pipelines/pipeline_details_bundle.js","lines":{"begin":77,"end":84}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 62**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5c1f719b9039c86ffdc704317d783449","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/labels/index/index.js","lines":{"begin":20,"end":24}},"remediation_points":780000,"other_locations":[{"path":"app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js","lines":{"begin":17,"end":21}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 61**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ab079e1e6bc28e1fc184b520beb34531","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js","lines":{"begin":17,"end":21}},"remediation_points":780000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/labels/index/index.js","lines":{"begin":20,"end":24}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 61**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e7369e08c5971c04639cff93271173af","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes/stores/mutations.js","lines":{"begin":56,"end":59}},"remediation_points":750000,"other_locations":[{"path":"app/assets/javascripts/notes/stores/mutations.js","lines":{"begin":61,"end":64}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 60**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1ad13a005d315ccf15cfe0584579158f","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes/stores/mutations.js","lines":{"begin":61,"end":64}},"remediation_points":750000,"other_locations":[{"path":"app/assets/javascripts/notes/stores/mutations.js","lines":{"begin":56,"end":59}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 60**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1ad13a005d315ccf15cfe0584579158f","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/sessions/new/username_validator.js","lines":{"begin":107,"end":111}},"remediation_points":750000,"other_locations":[{"path":"app/assets/javascripts/pages/sessions/new/username_validator.js","lines":{"begin":113,"end":117}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 60**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c9228663eecdf0fc1a8dbd811c863e38","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/sessions/new/username_validator.js","lines":{"begin":113,"end":117}},"remediation_points":750000,"other_locations":[{"path":"app/assets/javascripts/pages/sessions/new/username_validator.js","lines":{"begin":107,"end":111}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 60**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c9228663eecdf0fc1a8dbd811c863e38","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/droplab/hook_button.js","lines":{"begin":33,"end":36}},"remediation_points":720000,"other_locations":[{"path":"app/assets/javascripts/landing.js","lines":{"begin":18,"end":21}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 59**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"0cdcc38394199ec6567937ce812cb5a5","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/landing.js","lines":{"begin":18,"end":21}},"remediation_points":720000,"other_locations":[{"path":"app/assets/javascripts/droplab/hook_button.js","lines":{"begin":33,"end":36}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 59**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6b06657bea1bfe588af0efba4a2bf57e","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pipelines/pipeline_details_mediator.js","lines":{"begin":26,"end":31}},"remediation_points":720000,"other_locations":[{"path":"app/assets/javascripts/jobs/job_details_mediator.js","lines":{"begin":34,"end":39}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 59**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1f5e585cdfd268a3748dbdd584f69df8","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/job_details_mediator.js","lines":{"begin":34,"end":39}},"remediation_points":720000,"other_locations":[{"path":"app/assets/javascripts/pipelines/pipeline_details_mediator.js","lines":{"begin":26,"end":31}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 59**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"67d77d7b888d52cac1d666c6a95ba502","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/tree.js","lines":{"begin":38,"end":46}},"remediation_points":720000,"other_locations":[{"path":"app/assets/javascripts/tree.js","lines":{"begin":49,"end":57}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 59**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"16eb34236a934fb101dbe1e8b6ab4af1","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/tree.js","lines":{"begin":49,"end":57}},"remediation_points":720000,"other_locations":[{"path":"app/assets/javascripts/tree.js","lines":{"begin":38,"end":46}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 59**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"16eb34236a934fb101dbe1e8b6ab4af1","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":191,"end":203}},"remediation_points":690000,"other_locations":[{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":236,"end":248}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 58**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"13f117af1725bb929e7aa63491a25429","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":236,"end":248}},"remediation_points":690000,"other_locations":[{"path":"app/assets/javascripts/merge_conflicts/merge_conflict_store.js","lines":{"begin":191,"end":203}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 58**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"13f117af1725bb929e7aa63491a25429","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/groups/groups_filterable_list.js","lines":{"begin":7,"end":16}},"remediation_points":660000,"other_locations":[{"path":"app/assets/javascripts/diffs/store/utils.js","lines":{"begin":238,"end":247}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 57**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2a553868b7fea8b08596f9a95a22d484","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diffs/store/utils.js","lines":{"begin":238,"end":247}},"remediation_points":660000,"other_locations":[{"path":"app/assets/javascripts/groups/groups_filterable_list.js","lines":{"begin":7,"end":16}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 57**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"12bd1fc0fd53a6c025f6ed5d690be133","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js","lines":{"begin":45,"end":49}},"remediation_points":660000,"other_locations":[{"path":"app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js","lines":{"begin":51,"end":55}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 57**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e7e7f0899a668845138c08c503e337fe","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js","lines":{"begin":51,"end":55}},"remediation_points":660000,"other_locations":[{"path":"app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js","lines":{"begin":45,"end":49}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 57**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e7e7f0899a668845138c08c503e337fe","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js","lines":{"begin":8,"end":11}},"remediation_points":660000,"other_locations":[{"path":"app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js","lines":{"begin":13,"end":16}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 57**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"21bb124a570e4088d0684442dade767f","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js","lines":{"begin":13,"end":16}},"remediation_points":660000,"other_locations":[{"path":"app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js","lines":{"begin":8,"end":11}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 57**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"21bb124a570e4088d0684442dade767f","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/namespace_select.js","lines":{"begin":20,"end":26}},"remediation_points":630000,"other_locations":[{"path":"app/assets/javascripts/namespace_select.js","lines":{"begin":40,"end":46}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 56**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d22f4f9ddac4d361d9f6721a25ded899","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/namespace_select.js","lines":{"begin":40,"end":46}},"remediation_points":630000,"other_locations":[{"path":"app/assets/javascripts/namespace_select.js","lines":{"begin":20,"end":26}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 56**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d22f4f9ddac4d361d9f6721a25ded899","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/projects/project_new.js","lines":{"begin":103,"end":116}},"remediation_points":630000,"other_locations":[{"path":"app/assets/javascripts/ide/constants.js","lines":{"begin":43,"end":56}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 56**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"d9ad4d62a2c7b3a5ee02495f81a78cd8","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/constants.js","lines":{"begin":43,"end":56}},"remediation_points":630000,"other_locations":[{"path":"app/assets/javascripts/projects/project_new.js","lines":{"begin":103,"end":116}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 56**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"b9bd1f7a0fafe178e70f6a652d6fd8be","severity":"major","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_conflicts/components/diff_file_editor.js","lines":{"begin":13,"end":26}},"remediation_points":570000,"other_locations":[{"path":"app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js","lines":{"begin":8,"end":21}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 54**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"423d5a0cc874e941bd22657b325446b4","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js","lines":{"begin":8,"end":21}},"remediation_points":570000,"other_locations":[{"path":"app/assets/javascripts/merge_conflicts/components/diff_file_editor.js","lines":{"begin":13,"end":26}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 54**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"716df5a6c1cbc00338163aa638130bca","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":436,"end":441}},"remediation_points":570000,"other_locations":[{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":452,"end":457}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 54**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5a2cd4548ee8c0362f69cc88ee5982c0","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":452,"end":457}},"remediation_points":570000,"other_locations":[{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":436,"end":441}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 54**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5a2cd4548ee8c0362f69cc88ee5982c0","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":214,"end":221}},"remediation_points":570000,"other_locations":[{"path":"app/assets/javascripts/pages/search/init_filtered_search.js","lines":{"begin":13,"end":20}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 54**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e71451918dac542ee62e859e4a694a23","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/search/init_filtered_search.js","lines":{"begin":13,"end":20}},"remediation_points":570000,"other_locations":[{"path":"app/assets/javascripts/notes.js","lines":{"begin":214,"end":221}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 54**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6696916f5ca830f21733956d3dc83b22","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/prometheus_metrics/prometheus_metrics.js","lines":{"begin":45,"end":49}},"remediation_points":570000,"other_locations":[{"path":"app/assets/javascripts/prometheus_metrics/prometheus_metrics.js","lines":{"begin":50,"end":54}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 54**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"723e924a871cea3a66a3ccd49f2f4407","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/prometheus_metrics/prometheus_metrics.js","lines":{"begin":50,"end":54}},"remediation_points":570000,"other_locations":[{"path":"app/assets/javascripts/prometheus_metrics/prometheus_metrics.js","lines":{"begin":45,"end":49}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 54**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"723e924a871cea3a66a3ccd49f2f4407","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":97,"end":102}},"remediation_points":540000,"other_locations":[{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":103,"end":108}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 53**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"38cb87a7a94d43c42903f71cfd248953","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":103,"end":108}},"remediation_points":540000,"other_locations":[{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":97,"end":102}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 53**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"38cb87a7a94d43c42903f71cfd248953","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/projects/project_new.js","lines":{"begin":125,"end":129}},"remediation_points":540000,"other_locations":[{"path":"app/assets/javascripts/projects/project_new.js","lines":{"begin":150,"end":153}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 53**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"77fe9230b5c1c681ca23dd48b5bde881","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/projects/project_new.js","lines":{"begin":150,"end":153}},"remediation_points":540000,"other_locations":[{"path":"app/assets/javascripts/projects/project_new.js","lines":{"begin":125,"end":129}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 53**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"77fe9230b5c1c681ca23dd48b5bde881","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/reports/store/utils.js","lines":{"begin":39,"end":42}},"remediation_points":540000,"other_locations":[{"path":"app/assets/javascripts/reports/store/utils.js","lines":{"begin":44,"end":47}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 53**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6369d3d651e38cbbac285afad5297e9d","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/reports/store/utils.js","lines":{"begin":44,"end":47}},"remediation_points":540000,"other_locations":[{"path":"app/assets/javascripts/reports/store/utils.js","lines":{"begin":39,"end":42}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 53**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"6369d3d651e38cbbac285afad5297e9d","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/clusters/clusters_bundle.js","lines":{"begin":108,"end":111}},"remediation_points":510000,"other_locations":[{"path":"app/assets/javascripts/clusters/clusters_bundle.js","lines":{"begin":113,"end":116}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 52**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2b8d07121aa08a0ef9af946ea4e5d60d","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/clusters/clusters_bundle.js","lines":{"begin":113,"end":116}},"remediation_points":510000,"other_locations":[{"path":"app/assets/javascripts/clusters/clusters_bundle.js","lines":{"begin":108,"end":111}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 52**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2b8d07121aa08a0ef9af946ea4e5d60d","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/zen_mode.js","lines":{"begin":42,"end":45}},"remediation_points":510000,"other_locations":[{"path":"app/assets/javascripts/zen_mode.js","lines":{"begin":46,"end":49}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 52**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1c224ff513833f281d7bb58fb1280314","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/zen_mode.js","lines":{"begin":46,"end":49}},"remediation_points":510000,"other_locations":[{"path":"app/assets/javascripts/zen_mode.js","lines":{"begin":42,"end":45}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 52**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1c224ff513833f281d7bb58fb1280314","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions.js","lines":{"begin":133,"end":136}},"remediation_points":480000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions.js","lines":{"begin":144,"end":147}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 51**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5e09ce34a63a712c502dfcaa775c5a8e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/ide/stores/actions.js","lines":{"begin":144,"end":147}},"remediation_points":480000,"other_locations":[{"path":"app/assets/javascripts/ide/stores/actions.js","lines":{"begin":133,"end":136}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 51**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5e09ce34a63a712c502dfcaa775c5a8e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":120,"end":123}},"remediation_points":480000,"other_locations":[{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":128,"end":131}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 51**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5dcf5b9ed09acddcf34d9b99f4f7d4e0","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":128,"end":131}},"remediation_points":480000,"other_locations":[{"path":"app/assets/javascripts/network/branch_graph.js","lines":{"begin":120,"end":123}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 51**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5dcf5b9ed09acddcf34d9b99f4f7d4e0","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/projects/tree/services/commit_pipeline_service.js","lines":{"begin":3,"end":11}},"remediation_points":480000,"other_locations":[{"path":"app/assets/javascripts/jobs/services/job_service.js","lines":{"begin":3,"end":11}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 51**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"020d87eef79dcc6d4979a4877f795e88","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/services/job_service.js","lines":{"begin":3,"end":11}},"remediation_points":480000,"other_locations":[{"path":"app/assets/javascripts/projects/tree/services/commit_pipeline_service.js","lines":{"begin":3,"end":11}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 51**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"7f3c54ba927268843f9fe397b30c4541","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/star.js","lines":{"begin":19,"end":23}},"remediation_points":480000,"other_locations":[{"path":"app/assets/javascripts/star.js","lines":{"begin":23,"end":27}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 51**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"cc2a5b7fe6f4a900c4690f4960b1d2ce","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/star.js","lines":{"begin":23,"end":27}},"remediation_points":480000,"other_locations":[{"path":"app/assets/javascripts/star.js","lines":{"begin":19,"end":23}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 51**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"cc2a5b7fe6f4a900c4690f4960b1d2ce","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/groups/store/groups_store.js","lines":{"begin":102,"end":104}},"remediation_points":450000,"other_locations":[{"path":"app/assets/javascripts/boards/stores/modal_store.js","lines":{"begin":71,"end":74}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"e9ffa7b55a2d3d697297bb9c77d9e0c8","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/stores/modal_store.js","lines":{"begin":71,"end":74}},"remediation_points":450000,"other_locations":[{"path":"app/assets/javascripts/groups/store/groups_store.js","lines":{"begin":102,"end":104}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"cae962d929e29ddbbe69117801f17860","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/groups/issues/index.js","lines":{"begin":6,"end":13}},"remediation_points":450000,"other_locations":[{"path":"app/assets/javascripts/pages/groups/merge_requests/index.js","lines":{"begin":6,"end":13}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"65a33b3fe4ae20436c9fb46d65d9db04","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/groups/merge_requests/index.js","lines":{"begin":6,"end":13}},"remediation_points":450000,"other_locations":[{"path":"app/assets/javascripts/pages/groups/issues/index.js","lines":{"begin":6,"end":13}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1b0f3dfb819efd869e74a58517390763","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pipelines/pipeline_details_mediator.js","lines":{"begin":55,"end":58}},"remediation_points":450000,"other_locations":[{"path":"app/assets/javascripts/jobs/job_details_mediator.js","lines":{"begin":51,"end":54}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ed657cc5e6969316c8bd1d2be6204668","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/job_details_mediator.js","lines":{"begin":51,"end":54}},"remediation_points":450000,"other_locations":[{"path":"app/assets/javascripts/pipelines/pipeline_details_mediator.js","lines":{"begin":55,"end":58}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 50**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3da1a06772dc32dbbb550900c97f6cbd","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":125,"end":130}},"remediation_points":420000,"other_locations":[{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":154,"end":159}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 49**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f6c22427e43838beefc533b33cde9955","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":154,"end":159}},"remediation_points":420000,"other_locations":[{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":125,"end":130}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 49**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"f6c22427e43838beefc533b33cde9955","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/deploy_keys/service/index.js","lines":{"begin":15,"end":18}},"remediation_points":420000,"other_locations":[{"path":"app/assets/javascripts/deploy_keys/service/index.js","lines":{"begin":20,"end":23}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 49**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5eaf7d9cd8339dca959f74f384e02906","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/deploy_keys/service/index.js","lines":{"begin":20,"end":23}},"remediation_points":420000,"other_locations":[{"path":"app/assets/javascripts/deploy_keys/service/index.js","lines":{"begin":15,"end":18}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 49**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5eaf7d9cd8339dca959f74f384e02906","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":146,"end":148}},"remediation_points":420000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":250,"end":252}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 49**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"70155684217b5724087f40626f7766bc","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":250,"end":252}},"remediation_points":420000,"other_locations":[{"path":"app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js","lines":{"begin":146,"end":148}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 49**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"70155684217b5724087f40626f7766bc","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":134,"end":137}},"remediation_points":390000,"other_locations":[{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":138,"end":141}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 48**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"180e35eb524273ddfc92de366020a611","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":138,"end":141}},"remediation_points":390000,"other_locations":[{"path":"app/assets/javascripts/behaviors/markdown/copy_as_gfm.js","lines":{"begin":134,"end":137}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 48**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"180e35eb524273ddfc92de366020a611","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/issuable_bulk_update_sidebar.js","lines":{"begin":100,"end":106}},"remediation_points":390000,"other_locations":[{"path":"app/assets/javascripts/issuable_bulk_update_sidebar.js","lines":{"begin":117,"end":123}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 48**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"672a79b04a463defdd46c0086474fd52","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/issuable_bulk_update_sidebar.js","lines":{"begin":117,"end":123}},"remediation_points":390000,"other_locations":[{"path":"app/assets/javascripts/issuable_bulk_update_sidebar.js","lines":{"begin":100,"end":106}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 48**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"672a79b04a463defdd46c0086474fd52","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":66,"end":69}},"remediation_points":390000,"other_locations":[{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":146,"end":149}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 48**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"4ebfab6abe3fb873067f7b37190c9136","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":146,"end":149}},"remediation_points":390000,"other_locations":[{"path":"app/assets/javascripts/jobs/store/actions.js","lines":{"begin":66,"end":69}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 48**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"4ebfab6abe3fb873067f7b37190c9136","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/droplab/hook_input.js","lines":{"begin":46,"end":53}},"remediation_points":360000,"other_locations":[{"path":"app/assets/javascripts/droplab/hook_input.js","lines":{"begin":60,"end":67}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 47**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"10bdf4c525726570be1c10982443983e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/droplab/hook_input.js","lines":{"begin":60,"end":67}},"remediation_points":360000,"other_locations":[{"path":"app/assets/javascripts/droplab/hook_input.js","lines":{"begin":46,"end":53}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 47**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"10bdf4c525726570be1c10982443983e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/filtered_search/dropdown_hint.js","lines":{"begin":75,"end":77}},"remediation_points":360000,"other_locations":[{"path":"app/assets/javascripts/filtered_search/dropdown_user.js","lines":{"begin":78,"end":80}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 47**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"223d4160dbc2f173c7019bf4258bf71a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/filtered_search/dropdown_user.js","lines":{"begin":78,"end":80}},"remediation_points":360000,"other_locations":[{"path":"app/assets/javascripts/filtered_search/dropdown_hint.js","lines":{"begin":75,"end":77}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 47**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2f69c39865bb0c31ba27242592efb90a","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1451,"end":1457}},"remediation_points":360000,"other_locations":[{"path":"app/assets/javascripts/notes.js","lines":{"begin":1459,"end":1465}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 47**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"06432a7535afbcbc307fe2c2fad49f03","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/notes.js","lines":{"begin":1459,"end":1465}},"remediation_points":360000,"other_locations":[{"path":"app/assets/javascripts/notes.js","lines":{"begin":1451,"end":1457}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 47**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"06432a7535afbcbc307fe2c2fad49f03","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":63,"end":66}},"remediation_points":330000,"other_locations":[{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":423,"end":426}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 46**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3cf8b42abcd81551bbf1d4b6a86a23ec","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":423,"end":426}},"remediation_points":330000,"other_locations":[{"path":"app/assets/javascripts/create_merge_request_dropdown.js","lines":{"begin":63,"end":66}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 46**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"3cf8b42abcd81551bbf1d4b6a86a23ec","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js","lines":{"begin":223,"end":224}},"remediation_points":330000,"other_locations":[{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":564,"end":565}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 46**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9b59a169ec24710bb17e53176d9f6484","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/filtered_search/filtered_search_manager.js","lines":{"begin":564,"end":565}},"remediation_points":330000,"other_locations":[{"path":"app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js","lines":{"begin":223,"end":224}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 46**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"629f61554a8bb6a8eb033206d609a01e","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/gl_form.js","lines":{"begin":93,"end":95}},"remediation_points":330000,"other_locations":[{"path":"app/assets/javascripts/gl_form.js","lines":{"begin":96,"end":98}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 46**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"a37da012d8b9134f9c2c07cbe6e5df45","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/gl_form.js","lines":{"begin":96,"end":98}},"remediation_points":330000,"other_locations":[{"path":"app/assets/javascripts/gl_form.js","lines":{"begin":93,"end":95}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 46**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"a37da012d8b9134f9c2c07cbe6e5df45","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":48,"end":52}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":68,"end":72}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ad4849cc4103d257a75a53f533f93f88","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":68,"end":72}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/boards/models/issue.js","lines":{"begin":48,"end":52}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"ad4849cc4103d257a75a53f533f93f88","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/models/project.js","lines":{"begin":1,"end":6}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/boards/models/milestone.js","lines":{"begin":1,"end":6}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"63e808e699724c7876463b889a910d69","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/boards/models/milestone.js","lines":{"begin":1,"end":6}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/boards/models/project.js","lines":{"begin":1,"end":6}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2d888235869b541141524699d46a10fe","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diffs/store/mutations.js","lines":{"begin":142,"end":143}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/diffs/store/actions.js","lines":{"begin":139,"end":139}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"a5d03fc520fddde3ecc4ff06b86c9a0d","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/diffs/store/actions.js","lines":{"begin":139,"end":139}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/diffs/store/mutations.js","lines":{"begin":142,"end":143}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"5a0bd2adf62a695699f94e1cef624dd7","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":73,"end":76}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":78,"end":81}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1dc35d639071fed3c333a47b9c544d56","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":78,"end":81}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/merge_request_tabs.js","lines":{"begin":73,"end":76}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"1dc35d639071fed3c333a47b9c544d56","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/users/activity_calendar.js","lines":{"begin":234,"end":240}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/pages/users/activity_calendar.js","lines":{"begin":260,"end":266}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c9f4b3f70b4ef899508948c84a381a89","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/pages/users/activity_calendar.js","lines":{"begin":260,"end":266}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/pages/users/activity_calendar.js","lines":{"begin":234,"end":240}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"c9f4b3f70b4ef899508948c84a381a89","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/protected_tags/protected_tag_edit.js","lines":{"begin":16,"end":20}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/protected_branches/protected_branch_edit.js","lines":{"begin":24,"end":28}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"2d44f8e3653f3f06777c422f4151a7d3","severity":"minor","engine_name":"duplication"},{"type":"issue","check_name":"similar-code","description":"Similar blocks of code found in 2 locations. Consider refactoring.","categories":["Duplication"],"location":{"path":"app/assets/javascripts/protected_branches/protected_branch_edit.js","lines":{"begin":24,"end":28}},"remediation_points":300000,"other_locations":[{"path":"app/assets/javascripts/protected_tags/protected_tag_edit.js","lines":{"begin":16,"end":20}}],"content":{"body":"## Duplicated Code\n\nDuplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:\n\n> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nWhen you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).\n\n## Tuning\n\n**This issue has a mass of 45**.\n\nWe set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.\n\nThe threshold configuration represents the minimum [mass](https://docs.codeclimate.com/docs/duplication#mass) a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.\n\nIf the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.\n\nSee [`codeclimate-duplication`'s documentation](https://docs.codeclimate.com/docs/duplication) for more information about tuning the mass threshold in your `.codeclimate.yml`.\n\n## Refactorings\n\n* [Extract Method](http://sourcemaking.com/refactoring/extract-method)\n* [Extract Class](http://sourcemaking.com/refactoring/extract-class)\n* [Form Template Method](http://sourcemaking.com/refactoring/form-template-method)\n* [Introduce Null Object](http://sourcemaking.com/refactoring/introduce-null-object)\n* [Pull Up Method](http://sourcemaking.com/refactoring/pull-up-method)\n* [Pull Up Field](http://sourcemaking.com/refactoring/pull-up-field)\n* [Substitute Algorithm](http://sourcemaking.com/refactoring/substitute-algorithm)\n\n## Further Reading\n\n* [Don't Repeat Yourself](http://c2.com/cgi/wiki?DontRepeatYourself) on the C2 Wiki\n* [Duplicated Code](http://sourcemaking.com/refactoring/duplicated-code) on SourceMaking\n* [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672) by Martin Fowler. _Duplicated Code_, p76\n"},"fingerprint":"9ff0344a1b63248724d8413b03573b43","severity":"minor","engine_name":"duplication"},{"categories":["Security"],"check_name":"Insecure Dependency","content":{"body":"**Advisory**: CVE-2018-1000211\n\n**URL**: https://blog.justinbull.ca/cve-2018-1000211-public-apps-cant-revoke-tokens-in-doorkeeper/\n\n**Solution**: upgrade to >= 4.4.0, >= 5.0.0.rc2"},"description":"Doorkeeper gem does not revoke token for public clients","location":{"path":"Gemfile.lock","lines":{"begin":173,"end":173}},"remediation_points":5000000,"severity":"minor","type":"Issue","fingerprint":"13e50c564110ecb5eba441004fe52261","engine_name":"bundler-audit"},{"categories":["Security"],"check_name":"Insecure Dependency","content":{"body":"**Advisory**: CVE-2018-3769\n\n**URL**: https://github.com/ruby-grape/grape/issues/1762\n\n**Solution**: upgrade to >= 1.1.0"},"description":"ruby-grape Gem has XSS via \"format\" parameter","location":{"path":"Gemfile.lock","lines":{"begin":346,"end":346}},"remediation_points":5000000,"severity":"minor","type":"Issue","fingerprint":"ac6d981abbf80f299374d5a1081c7d66","engine_name":"bundler-audit"}]
diff --git a/spec/fixtures/codequality/codequality.json.gz b/spec/fixtures/codequality/codequality.json.gz
deleted file mode 100644
index f04ec0065b4..00000000000
--- a/spec/fixtures/codequality/codequality.json.gz
+++ /dev/null
Binary files differ
diff --git a/spec/fixtures/csv_empty.csv b/spec/fixtures/csv_empty.csv
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/spec/fixtures/csv_empty.csv
diff --git a/spec/fixtures/csv_uppercase.CSV b/spec/fixtures/csv_uppercase.CSV
new file mode 100644
index 00000000000..bef22ed9ddf
--- /dev/null
+++ b/spec/fixtures/csv_uppercase.CSV
@@ -0,0 +1,4 @@
+TITLE,DESCRIPTION
+Issue in 中文,Test description
+"Hello","World"
+"Title with quote""",Description
diff --git a/spec/fixtures/dependency_proxy/manifest b/spec/fixtures/dependency_proxy/manifest
new file mode 100644
index 00000000000..a899d05d697
--- /dev/null
+++ b/spec/fixtures/dependency_proxy/manifest
@@ -0,0 +1,38 @@
+{
+ "schemaVersion": 1,
+ "name": "library/alpine",
+ "tag": "latest",
+ "architecture": "amd64",
+ "fsLayers": [
+ {
+ "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
+ },
+ {
+ "blobSum": "sha256:188c0c94c7c576fff0792aca7ec73d67a2f7f4cb3a6e53a84559337260b36964"
+ }
+ ],
+ "history": [
+ {
+ "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\"],\"ArgsEscaped\":true,\"Image\":\"sha256:3543079adc6fb5170279692361be8b24e89ef1809a374c1b4429e1d560d1459c\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"container\":\"8c59eb170e19b8c3768b8d06c91053b0debf4a6fa6a452df394145fe9b885ea5\",\"container_config\":{\"Hostname\":\"8c59eb170e19\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) \",\"CMD [\\\"/bin/sh\\\"]\"],\"ArgsEscaped\":true,\"Image\":\"sha256:3543079adc6fb5170279692361be8b24e89ef1809a374c1b4429e1d560d1459c\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"created\":\"2020-10-22T02:19:24.499382102Z\",\"docker_version\":\"18.09.7\",\"id\":\"c5f1aab5bb88eaf1aa62bea08ea6654547d43fd4d15b1a476c77e705dd5385ba\",\"os\":\"linux\",\"parent\":\"dc0b50cc52bc340d7848a62cfe8a756f4420592f4984f7a680ef8f9d258176ed\",\"throwaway\":true}"
+ },
+ {
+ "v1Compatibility": "{\"id\":\"dc0b50cc52bc340d7848a62cfe8a756f4420592f4984f7a680ef8f9d258176ed\",\"created\":\"2020-10-22T02:19:24.33416307Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:f17f65714f703db9012f00e5ec98d0b2541ff6147c2633f7ab9ba659d0c507f4 in / \"]}}"
+ }
+ ],
+ "signatures": [
+ {
+ "header": {
+ "jwk": {
+ "crv": "P-256",
+ "kid": "XOTE:DZ4C:YBPJ:3O3L:YI4B:NYXU:T4VR:USH6:CXXN:SELU:CSCC:FVPE",
+ "kty": "EC",
+ "x": "cR1zye_3354mdbD7Dn-mtXNXvtPtmLlUVDa5vH6Lp74",
+ "y": "rldUXSllLit6_2BW6AV8aqkwWJXHoYPG9OwkIBouwxQ"
+ },
+ "alg": "ES256"
+ },
+ "signature": "DYB2iB-XKIisqp5Q0OXFOBIOlBOuRV7pnZuKy0cxVB2Qj1VFRhWX4Tq336y0VMWbF6ma1he5A1E_Vk4jazrJ9g",
+ "protected": "eyJmb3JtYXRMZW5ndGgiOjIxMzcsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMC0xMS0yNFQyMjowMTo1MVoifQ"
+ }
+ ]
+} \ No newline at end of file
diff --git a/spec/fixtures/lib/gitlab/import_export/designs/project.json b/spec/fixtures/lib/gitlab/import_export/designs/project.json
index e11b10a1d4c..1d51a726c4c 100644
--- a/spec/fixtures/lib/gitlab/import_export/designs/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/designs/project.json
@@ -61,7 +61,7 @@
"lock_version":0,
"time_estimate":0,
"relative_position":1073742323,
- "service_desk_reply_to":null,
+ "external_author":null,
"last_edited_at":null,
"last_edited_by_id":null,
"discussion_locked":null,
@@ -244,7 +244,7 @@
"lock_version":0,
"time_estimate":0,
"relative_position":1073742823,
- "service_desk_reply_to":null,
+ "external_author":null,
"last_edited_at":null,
"last_edited_by_id":null,
"discussion_locked":null,
diff --git a/spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/4351.json b/spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/4351.json
new file mode 100644
index 00000000000..ce657ded7b0
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/4351.json
@@ -0,0 +1 @@
+{"name":"ymg09t5704clnxnqfgaj2h098gz4r7gyx4wc3fzmlqj1en24zf","path":"ymg09t5704clnxnqfgaj2h098gz4r7gyx4wc3fzmlqj1en24zf","owner_id":123,"created_at":"2019-11-20 17:01:53 UTC","updated_at":"2019-11-20 17:05:44 UTC","description":"Group Description","avatar":{"url":null},"membership_lock":false,"share_with_group_lock":false,"visibility_level":0,"request_access_enabled":true,"ldap_sync_status":"ready","ldap_sync_error":null,"ldap_sync_last_update_at":null,"ldap_sync_last_successful_update_at":null,"ldap_sync_last_sync_at":null,"lfs_enabled":null,"parent_id":null,"shared_runners_minutes_limit":null,"repository_size_limit":null,"require_two_factor_authentication":false,"two_factor_grace_period":48,"plan_id":null,"project_creation_level":2,"trial_ends_on":null,"file_template_project_id":null,"saml_discovery_token":"rBKx3ioz","custom_project_templates_group_id":null,"auto_devops_enabled":null,"extra_shared_runners_minutes_limit":null,"last_ci_minutes_notification_at":null,"last_ci_minutes_usage_notification_level":null,"runners_token":"token","runners_token_encrypted":"encrypted","subgroup_creation_level":1,"emails_disabled":null,"max_pages_size":null,"max_artifacts_size":null,"id":4351} \ No newline at end of file
diff --git a/spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/4352.json b/spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/4352.json
new file mode 100644
index 00000000000..d57f23f0222
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/4352.json
@@ -0,0 +1 @@
+{"name":"a","path":"a","owner_id":null,"created_at":"2019-11-20 17:01:53 UTC","updated_at":"2019-11-20 17:05:44 UTC","description":"","avatar":{"url":null},"membership_lock":false,"share_with_group_lock":false,"visibility_level":0,"request_access_enabled":true,"ldap_sync_status":"ready","ldap_sync_error":null,"ldap_sync_last_update_at":null,"ldap_sync_last_successful_update_at":null,"ldap_sync_last_sync_at":null,"lfs_enabled":null,"parent_id":4351,"shared_runners_minutes_limit":null,"repository_size_limit":null,"require_two_factor_authentication":false,"two_factor_grace_period":48,"plan_id":null,"project_creation_level":2,"trial_ends_on":null,"file_template_project_id":null,"saml_discovery_token":"ki3Xnjw3","custom_project_templates_group_id":null,"auto_devops_enabled":null,"extra_shared_runners_minutes_limit":null,"last_ci_minutes_notification_at":null,"last_ci_minutes_usage_notification_level":null,"subgroup_creation_level":1,"emails_disabled":null,"max_pages_size":null,"max_artifacts_size":null,"id":4352}
diff --git a/spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/_all.ndjson b/spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/_all.ndjson
new file mode 100644
index 00000000000..078909d30fb
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/import_export/group_exports/child_short_name/tree/groups/_all.ndjson
@@ -0,0 +1,2 @@
+4351
+4352
diff --git a/spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree/project.json b/spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree/project.json
new file mode 100644
index 00000000000..12136c6df3b
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree/project.json
@@ -0,0 +1 @@
+{"description":"Nisi et repellendus ut enim quo accusamus vel magnam.","import_type":"gitlab_project","creator_id":2147483547,"visibility_level":10,"archived":false,"hooks":[]}
diff --git a/spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree/project/labels.ndjson b/spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree/project/labels.ndjson
new file mode 100644
index 00000000000..34a2f48ac87
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree/project/labels.ndjson
@@ -0,0 +1 @@
+{"id":2,"title":"","color":"#428bca","project_id":8,"created_at":"2016-07-22T08:55:44.161Z","updated_at":"2016-07-22T08:55:44.161Z","template":false,"description":"","type":"","priorities":[]}
diff --git a/spec/fixtures/lib/gitlab/performance_bar/peek_data.json b/spec/fixtures/lib/gitlab/performance_bar/peek_data.json
new file mode 100644
index 00000000000..8e207b69ecb
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/performance_bar/peek_data.json
@@ -0,0 +1,104 @@
+{
+ "context": {},
+ "data": {
+ "host": {
+ "hostname": "pc",
+ "canary": null
+ },
+ "active-record": {
+ "duration": "6ms",
+ "calls": "7 (0 cached)",
+ "details": [
+ {
+ "duration": 1.096,
+ "sql": "SELECT COUNT(*) FROM ((SELECT \"badges\".* FROM \"badges\" WHERE \"badges\".\"type\" = 'ProjectBadge' AND \"badges\".\"project_id\" = 8)\nUNION\n(SELECT \"badges\".* FROM \"badges\" WHERE \"badges\".\"type\" = 'GroupBadge' AND \"badges\".\"group_id\" IN (SELECT \"namespaces\".\"id\" FROM \"namespaces\" WHERE \"namespaces\".\"type\" = 'Group' AND \"namespaces\".\"id\" = 28))) badges",
+ "backtrace": [
+ "lib/gitlab/pagination/offset_pagination.rb:53:in `add_pagination_headers'",
+ "lib/gitlab/pagination/offset_pagination.rb:15:in `block in paginate'",
+ "lib/gitlab/pagination/offset_pagination.rb:14:in `tap'",
+ "lib/gitlab/pagination/offset_pagination.rb:14:in `paginate'",
+ "lib/api/helpers/pagination.rb:7:in `paginate'",
+ "lib/api/badges.rb:42:in `block (3 levels) in <class:Badges>'",
+ "ee/lib/gitlab/ip_address_state.rb:10:in `with'",
+ "lib/api/api_guard.rb:208:in `call'",
+ "lib/gitlab/jira/middleware.rb:19:in `call'"
+ ],
+ "cached": "",
+ "warnings": []
+ },
+ {
+ "duration": 0.817,
+ "sql": "SELECT \"projects\".* FROM \"projects\" WHERE \"projects\".\"pending_delete\" = $1 AND \"projects\".\"id\" = $2 LIMIT $3",
+ "backtrace": [
+ "lib/api/helpers.rb:112:in `find_project'",
+ "ee/lib/ee/api/helpers.rb:88:in `find_project!'",
+ "lib/api/helpers/members_helpers.rb:14:in `public_send'",
+ "lib/api/helpers/members_helpers.rb:14:in `find_source'",
+ "lib/api/badges.rb:36:in `block (3 levels) in <class:Badges>'",
+ "ee/lib/gitlab/ip_address_state.rb:10:in `with'",
+ "lib/api/api_guard.rb:208:in `call'",
+ "lib/gitlab/jira/middleware.rb:19:in `call'"
+ ],
+ "cached": "",
+ "warnings": []
+ },
+ {
+ "duration": 0.817,
+ "sql": "SELECT \"projects\".* FROM \"projects\" WHERE \"projects\".\"pending_delete\" = $1 AND \"projects\".\"id\" = $2 LIMIT $3",
+ "backtrace": [
+ "lib/api/helpers.rb:112:in `find_project'",
+ "ee/lib/ee/api/helpers.rb:88:in `find_project!'",
+ "lib/api/helpers/members_helpers.rb:14:in `public_send'",
+ "lib/api/helpers/members_helpers.rb:14:in `find_source'",
+ "lib/api/badges.rb:36:in `block (3 levels) in <class:Badges>'",
+ "ee/lib/gitlab/ip_address_state.rb:10:in `with'",
+ "lib/api/api_guard.rb:208:in `call'",
+ "lib/gitlab/jira/middleware.rb:19:in `call'"
+ ],
+ "cached": "",
+ "warnings": []
+ }
+ ],
+ "warnings": []
+ },
+ "gitaly": {
+ "duration": "0ms",
+ "calls": 0,
+ "details": [],
+ "warnings": []
+ },
+ "redis": {
+ "duration": "0ms",
+ "calls": 1,
+ "details": [
+ {
+ "cmd": "get cache:gitlab:flipper/v1/feature/api_kaminari_count_with_limit",
+ "duration": 0.155,
+ "backtrace": [
+ "lib/gitlab/instrumentation/redis_interceptor.rb:30:in `call'",
+ "lib/feature.rb:81:in `enabled?'",
+ "lib/gitlab/pagination/offset_pagination.rb:30:in `paginate_with_limit_optimization'",
+ "lib/gitlab/pagination/offset_pagination.rb:14:in `paginate'",
+ "lib/api/helpers/pagination.rb:7:in `paginate'",
+ "lib/api/badges.rb:42:in `block (3 levels) in <class:Badges>'",
+ "ee/lib/gitlab/ip_address_state.rb:10:in `with'",
+ "lib/api/api_guard.rb:208:in `call'",
+ "lib/gitlab/jira/middleware.rb:19:in `call'"
+ ],
+ "storage": "Cache",
+ "warnings": [],
+ "instance": "Cache"
+ }
+ ],
+ "warnings": []
+ },
+ "es": {
+ "duration": "0ms",
+ "calls": 0,
+ "details": [],
+ "warnings": []
+ }
+ },
+ "has_warnings": false
+}
+
diff --git a/spec/fixtures/valid.po b/spec/fixtures/valid.po
index c9b68d16153..e580af66939 100644
--- a/spec/fixtures/valid.po
+++ b/spec/fixtures/valid.po
@@ -8,6 +8,7 @@ msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2017-07-13 12:10-0500\n"
+"PO-Creation-Date: 2016-07-13 12:11-0500\n"
"Language-Team: Spanish\n"
"Language: es\n"
"MIME-Version: 1.0\n"
diff --git a/spec/fixtures/whats_new/20201225_01_01.yml b/spec/fixtures/whats_new/20201225_01_01.yml
index 06db95be44f..7bf58900cc7 100644
--- a/spec/fixtures/whats_new/20201225_01_01.yml
+++ b/spec/fixtures/whats_new/20201225_01_01.yml
@@ -1,2 +1,7 @@
---
- title: It's gonna be a bright
+ body: |
+ ## It's gonna be a bright
+ self-managed: true
+ gitlab-com: false
+ packages: ["Premium", "Ultimate"]
diff --git a/spec/fixtures/whats_new/20201225_01_02.yml b/spec/fixtures/whats_new/20201225_01_02.yml
index 91b0bd7036e..90b5192897e 100644
--- a/spec/fixtures/whats_new/20201225_01_02.yml
+++ b/spec/fixtures/whats_new/20201225_01_02.yml
@@ -1,2 +1,7 @@
---
- title: bright
+ body: |
+ ## bright
+ self-managed: true
+ gitlab-com: false
+ packages: ["Premium", "Ultimate"]
diff --git a/spec/fixtures/whats_new/20201225_01_05.yml b/spec/fixtures/whats_new/20201225_01_05.yml
index 7c95e386f00..27c8f989b08 100644
--- a/spec/fixtures/whats_new/20201225_01_05.yml
+++ b/spec/fixtures/whats_new/20201225_01_05.yml
@@ -1,3 +1,14 @@
---
- title: bright and sunshinin' day
+ body: |
+ ## bright and sunshinin' day
+ self-managed: true
+ gitlab-com: false
+ packages: ["Premium", "Ultimate"]
release: '01.05'
+- title: I think I can make it now the pain is gone
+ body: |
+ ## I think I can make it now the pain is gone
+ self-managed: false
+ gitlab-com: true
+ packages: ["Premium", "Ultimate"]
diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml
index 8e6faa90c58..d0e585e844a 100644
--- a/spec/frontend/.eslintrc.yml
+++ b/spec/frontend/.eslintrc.yml
@@ -25,3 +25,6 @@ rules:
- 'testAction'
jest/no-test-callback:
- off
+ "@gitlab/no-global-event-off":
+ - off
+
diff --git a/spec/frontend/__mocks__/@toast-ui/vue-editor/index.js b/spec/frontend/__mocks__/@toast-ui/vue-editor/index.js
index 9fee8e18d26..2acd8111c77 100644
--- a/spec/frontend/__mocks__/@toast-ui/vue-editor/index.js
+++ b/spec/frontend/__mocks__/@toast-ui/vue-editor/index.js
@@ -1,3 +1,12 @@
+export const mockEditorApi = {
+ eventManager: {
+ addEventType: jest.fn(),
+ listen: jest.fn(),
+ removeEventHandler: jest.fn(),
+ },
+ getMarkdown: jest.fn(),
+};
+
export const Editor = {
props: {
initialValue: {
@@ -18,14 +27,6 @@ export const Editor = {
},
},
created() {
- const mockEditorApi = {
- eventManager: {
- addEventType: jest.fn(),
- listen: jest.fn(),
- removeEventHandler: jest.fn(),
- },
- };
-
this.$emit('load', mockEditorApi);
},
render(h) {
diff --git a/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap b/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap
index 5fad0d07f97..8948a9926bb 100644
--- a/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap
+++ b/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap
@@ -18,7 +18,9 @@ exports[`AddContextCommitsModal renders modal with 2 tabs 1`] = `
theme="indigo"
value="0"
>
- <gl-tab-stub>
+ <gl-tab-stub
+ titlelinkclass=""
+ >
<div
class="mt-2"
@@ -37,7 +39,9 @@ exports[`AddContextCommitsModal renders modal with 2 tabs 1`] = `
</div>
</gl-tab-stub>
- <gl-tab-stub>
+ <gl-tab-stub
+ titlelinkclass=""
+ >
<review-tab-container-stub
commits=""
diff --git a/spec/frontend/admin/users/components/app_spec.js b/spec/frontend/admin/users/components/app_spec.js
new file mode 100644
index 00000000000..65b13e3a40d
--- /dev/null
+++ b/spec/frontend/admin/users/components/app_spec.js
@@ -0,0 +1,37 @@
+import { shallowMount } from '@vue/test-utils';
+
+import AdminUsersApp from '~/admin/users/components/app.vue';
+import AdminUsersTable from '~/admin/users/components/users_table.vue';
+import { users, paths } from '../mock_data';
+
+describe('AdminUsersApp component', () => {
+ let wrapper;
+
+ const initComponent = (props = {}) => {
+ wrapper = shallowMount(AdminUsersApp, {
+ propsData: {
+ users,
+ paths,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when initialized', () => {
+ beforeEach(() => {
+ initComponent();
+ });
+
+ it('renders the admin users table with props', () => {
+ expect(wrapper.find(AdminUsersTable).props()).toEqual({
+ users,
+ paths,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/admin/users/components/users_table_spec.js b/spec/frontend/admin/users/components/users_table_spec.js
new file mode 100644
index 00000000000..ba36e1e32ef
--- /dev/null
+++ b/spec/frontend/admin/users/components/users_table_spec.js
@@ -0,0 +1,61 @@
+import { GlTable } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+
+import AdminUsersTable from '~/admin/users/components/users_table.vue';
+import { users, paths } from '../mock_data';
+
+describe('AdminUsersTable component', () => {
+ let wrapper;
+
+ const getCellByLabel = (trIdx, label) => {
+ return wrapper
+ .find(GlTable)
+ .find('tbody')
+ .findAll('tr')
+ .at(trIdx)
+ .find(`[data-label="${label}"][role="cell"]`);
+ };
+
+ const initComponent = (props = {}) => {
+ wrapper = mount(AdminUsersTable, {
+ propsData: {
+ users,
+ paths,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when there are users', () => {
+ const user = users[0];
+
+ beforeEach(() => {
+ initComponent();
+ });
+
+ it.each`
+ key | label
+ ${'name'} | ${'Name'}
+ ${'projectsCount'} | ${'Projects'}
+ ${'createdAt'} | ${'Created on'}
+ ${'lastActivityOn'} | ${'Last activity'}
+ `('renders users.$key for $label', ({ key, label }) => {
+ expect(getCellByLabel(0, label).text()).toBe(`${user[key]}`);
+ });
+ });
+
+ describe('when users is an empty array', () => {
+ beforeEach(() => {
+ initComponent({ users: [] });
+ });
+
+ it('renders a "No users found" message', () => {
+ expect(wrapper.text()).toContain('No users found');
+ });
+ });
+});
diff --git a/spec/frontend/admin/users/index_spec.js b/spec/frontend/admin/users/index_spec.js
new file mode 100644
index 00000000000..171d54c8f4f
--- /dev/null
+++ b/spec/frontend/admin/users/index_spec.js
@@ -0,0 +1,35 @@
+import { createWrapper } from '@vue/test-utils';
+import initAdminUsers from '~/admin/users';
+import AdminUsersApp from '~/admin/users/components/app.vue';
+import { users, paths } from './mock_data';
+
+describe('initAdminUsersApp', () => {
+ let wrapper;
+ let el;
+
+ const findApp = () => wrapper.find(AdminUsersApp);
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ el.setAttribute('data-users', JSON.stringify(users));
+ el.setAttribute('data-paths', JSON.stringify(paths));
+
+ document.body.appendChild(el);
+
+ wrapper = createWrapper(initAdminUsers(el));
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ el.remove();
+ el = null;
+ });
+
+ it('parses and passes props', () => {
+ expect(findApp().props()).toMatchObject({
+ users,
+ paths,
+ });
+ });
+});
diff --git a/spec/frontend/admin/users/mock_data.js b/spec/frontend/admin/users/mock_data.js
new file mode 100644
index 00000000000..62fa9469638
--- /dev/null
+++ b/spec/frontend/admin/users/mock_data.js
@@ -0,0 +1,29 @@
+export const users = [
+ {
+ id: 2177,
+ name: 'Nikki',
+ createdAt: '2020-11-13T12:26:54.177Z',
+ email: 'nikki@example.com',
+ username: 'nikki',
+ lastActivityOn: '2020-12-09',
+ avatarUrl:
+ 'https://secure.gravatar.com/avatar/054f062d8b1a42b123f17e13a173cda8?s=80\\u0026d=identicon',
+ badges: [],
+ projectsCount: 0,
+ actions: [],
+ },
+];
+
+export const paths = {
+ edit: '/admin/users/id/edit',
+ approve: '/admin/users/id/approve',
+ reject: '/admin/users/id/reject',
+ unblock: '/admin/users/id/unblock',
+ block: '/admin/users/id/block',
+ deactivate: '/admin/users/id/deactivate',
+ activate: '/admin/users/id/activate',
+ unlock: '/admin/users/id/unlock',
+ delete: '/admin/users/id',
+ deleteWithContributions: '/admin/users/id',
+ adminUser: '/admin/users/id',
+};
diff --git a/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js b/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js
index 1d87301aac9..6430273ec59 100644
--- a/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js
+++ b/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js
@@ -179,7 +179,7 @@ describe('Alert Details Sidebar Assignees', () => {
findAssigned()
.find('.dropdown-menu-user-username')
.text(),
- ).toBe('root');
+ ).toBe('@root');
});
});
});
diff --git a/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js b/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
index 5574c83eb76..ed78a593944 100644
--- a/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
+++ b/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
@@ -42,8 +42,8 @@ describe('AlertsServiceForm', () => {
mockAxios = new MockAdapter(axios);
setFixtures(`
<div>
- <span class="js-service-active-status fa fa-circle" data-value="true"></span>
- <span class="js-service-active-status fa fa-power-off" data-value="false"></span>
+ <span class="js-service-active-status" data-value="true"><svg class="s16 cgreen" data-testid="check-icon"><use xlink:href="icons.svg#check" /></svg></span>
+ <span class="js-service-active-status" data-value="false"><svg class="s16 clgray" data-testid="power-icon"><use xlink:href="icons.svg#power" /></svg></span>
</div>`);
});
diff --git a/spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_old_spec.js.snap b/spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_old_spec.js.snap
deleted file mode 100644
index 9306bf24baf..00000000000
--- a/spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_old_spec.js.snap
+++ /dev/null
@@ -1,47 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`AlertsSettingsFormOld with default values renders the initial template 1`] = `
-"<gl-form-stub>
- <h5 class=\\"gl-font-lg gl-my-5\\"></h5>
- <!---->
- <div data-testid=\\"alert-settings-description\\">
- <p>
- <gl-sprintf-stub message=\\"You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.\\"></gl-sprintf-stub>
- </p>
- <p>
- <gl-sprintf-stub message=\\"Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.\\"></gl-sprintf-stub>
- </p>
- </div>
- <gl-form-group-stub label-for=\\"integration-type\\" label=\\"Integration\\">
- <gl-form-select-stub id=\\"integration-type\\" options=\\"[object Object],[object Object],[object Object]\\" data-testid=\\"alert-settings-select\\" value=\\"HTTP\\"></gl-form-select-stub> <span class=\\"gl-text-gray-500\\"><gl-sprintf-stub message=\\"Learn more about our our upcoming %{linkStart}integrations%{linkEnd}\\"></gl-sprintf-stub></span>
- </gl-form-group-stub>
- <gl-form-group-stub label=\\"Active\\" label-for=\\"active\\">
- <toggle-button-stub id=\\"active\\"></toggle-button-stub>
- </gl-form-group-stub>
- <!---->
- <gl-form-group-stub label=\\"Webhook URL\\" label-for=\\"url\\">
- <gl-form-input-group-stub value=\\"/alerts/notify.json\\" predefinedoptions=\\"[object Object]\\" id=\\"url\\" readonly=\\"\\"></gl-form-input-group-stub> <span class=\\"gl-text-gray-500\\">
-
- </span>
- </gl-form-group-stub>
- <gl-form-group-stub label-for=\\"authorization-key\\">
- <gl-form-input-group-stub value=\\"\\" predefinedoptions=\\"[object Object]\\" id=\\"authorization-key\\" readonly=\\"\\" class=\\"gl-mb-2\\"></gl-form-input-group-stub>
- <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\" class=\\"gl-mt-3\\" role=\\"button\\" tabindex=\\"0\\">Reset key</gl-button-stub>
- <gl-modal-stub modalid=\\"tokenModal\\" titletag=\\"h4\\" modalclass=\\"\\" size=\\"md\\" title=\\"Reset key\\" ok-title=\\"Reset key\\" ok-variant=\\"danger\\">
- Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.
- </gl-modal-stub>
- </gl-form-group-stub>
- <gl-form-group-stub label=\\"Alert test payload\\" label-for=\\"alert-json\\">
- <gl-form-textarea-stub noresize=\\"true\\" id=\\"alert-json\\" disabled=\\"true\\" state=\\"true\\" placeholder=\\"Enter test alert JSON....\\" rows=\\"6\\" max-rows=\\"10\\"></gl-form-textarea-stub>
- </gl-form-group-stub>
- <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\">Test alert payload</gl-button-stub>
- <div class=\\"footer-block row-content-block gl-display-flex gl-justify-content-space-between\\">
- <gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\">
- Save changes
- </gl-button-stub>
- <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\">
- Cancel
- </gl-button-stub>
- </div>
-</gl-form-stub>"
-`;
diff --git a/spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_new_spec.js.snap b/spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_spec.js.snap
index e2ef7483316..a1ced8910b3 100644
--- a/spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_new_spec.js.snap
+++ b/spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_spec.js.snap
@@ -28,7 +28,7 @@ exports[`AlertsSettingsFormNew with default values renders the initial template
<div id=\\"integration-webhook\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"integration-webhook__BV_label_\\" for=\\"integration-webhook\\" class=\\"d-block col-form-label\\">3. Set up webhook</label>
<div class=\\"bv-no-focus-ring\\"><span>Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the <a rel=\\"noopener noreferrer\\" target=\\"_blank\\" href=\\"https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html\\" class=\\"gl-link gl-display-inline-block\\">GitLab documentation</a> to learn more about configuring your endpoint.</span> <label class=\\"gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content gl-my-4 gl-font-weight-normal\\">
<div class=\\"gl-toggle-wrapper\\"><span class=\\"gl-toggle-label\\">Active</span>
- <!----> <button aria-label=\\"Active\\" type=\\"button\\" class=\\"gl-toggle\\"><span class=\\"toggle-icon\\"><svg data-testid=\\"close-icon\\" class=\\"gl-icon s16\\"><use href=\\"#close\\"></use></svg></span></button></div>
+ <!----> <button aria-label=\\"Active\\" type=\\"button\\" class=\\"gl-toggle\\"><span class=\\"toggle-icon\\"><svg data-testid=\\"close-icon\\" aria-hidden=\\"true\\" class=\\"gl-icon s16\\"><use href=\\"#close\\"></use></svg></span></button></div>
<!---->
</label>
<!---->
@@ -40,7 +40,7 @@ exports[`AlertsSettingsFormNew with default values renders the initial template
<!---->
<!----> <input id=\\"url\\" type=\\"text\\" readonly=\\"readonly\\" class=\\"gl-form-input form-control\\">
<div class=\\"input-group-append\\"><button title=\\"Copy\\" data-clipboard-text=\\"\\" aria-label=\\"Copy this value\\" type=\\"button\\" class=\\"btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon\\">
- <!----> <svg data-testid=\\"copy-to-clipboard-icon\\" class=\\"gl-button-icon gl-icon s16\\">
+ <!----> <svg data-testid=\\"copy-to-clipboard-icon\\" aria-hidden=\\"true\\" class=\\"gl-button-icon gl-icon s16\\">
<use href=\\"#copy-to-clipboard\\"></use>
</svg>
<!----></button></div>
@@ -56,7 +56,7 @@ exports[`AlertsSettingsFormNew with default values renders the initial template
<!---->
<!----> <input id=\\"authorization-key\\" type=\\"text\\" readonly=\\"readonly\\" class=\\"gl-form-input form-control\\">
<div class=\\"input-group-append\\"><button title=\\"Copy\\" data-clipboard-text=\\"\\" aria-label=\\"Copy this value\\" type=\\"button\\" class=\\"btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon\\">
- <!----> <svg data-testid=\\"copy-to-clipboard-icon\\" class=\\"gl-button-icon gl-icon s16\\">
+ <!----> <svg data-testid=\\"copy-to-clipboard-icon\\" aria-hidden=\\"true\\" class=\\"gl-button-icon gl-icon s16\\">
<use href=\\"#copy-to-clipboard\\"></use>
</svg>
<!----></button></div>
@@ -87,7 +87,7 @@ exports[`AlertsSettingsFormNew with default values renders the initial template
<div class=\\"gl-display-flex gl-justify-content-start gl-py-3\\"><button data-testid=\\"integration-form-submit\\" type=\\"submit\\" class=\\"btn js-no-auto-disable btn-success btn-md gl-button\\">
<!---->
<!----> <span class=\\"gl-button-text\\">Save integration
- </span></button> <button data-testid=\\"integration-test-and-submit\\" type=\\"button\\" class=\\"btn gl-mx-3 js-no-auto-disable btn-success btn-md gl-button btn-success-secondary\\">
+ </span></button> <button data-testid=\\"integration-test-and-submit\\" type=\\"button\\" disabled=\\"disabled\\" class=\\"btn gl-mx-3 js-no-auto-disable btn-success btn-md disabled gl-button btn-success-secondary\\">
<!---->
<!----> <span class=\\"gl-button-text\\">Save and test payload</span></button> <button type=\\"reset\\" class=\\"btn js-no-auto-disable btn-default btn-md gl-button\\">
<!---->
diff --git a/spec/frontend/alerts_settings/alerts_integrations_list_spec.js b/spec/frontend/alerts_settings/alerts_integrations_list_spec.js
index 90bb38f0c2b..3a7392f64f7 100644
--- a/spec/frontend/alerts_settings/alerts_integrations_list_spec.js
+++ b/spec/frontend/alerts_settings/alerts_integrations_list_spec.js
@@ -35,9 +35,6 @@ describe('AlertIntegrationsList', () => {
integrations: mockIntegrations,
...props,
},
- provide: {
- glFeatures: { httpIntegrationsList: true },
- },
stubs: {
GlIcon: true,
GlButton: true,
diff --git a/spec/frontend/alerts_settings/alerts_settings_form_old_spec.js b/spec/frontend/alerts_settings/alerts_settings_form_old_spec.js
deleted file mode 100644
index 3d0dfb44d63..00000000000
--- a/spec/frontend/alerts_settings/alerts_settings_form_old_spec.js
+++ /dev/null
@@ -1,204 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlModal, GlAlert } from '@gitlab/ui';
-import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form_old.vue';
-import ToggleButton from '~/vue_shared/components/toggle_button.vue';
-import { i18n } from '~/alerts_settings/constants';
-import service from '~/alerts_settings/services';
-import { defaultAlertSettingsConfig } from './util';
-
-jest.mock('~/alerts_settings/services');
-
-describe('AlertsSettingsFormOld', () => {
- let wrapper;
-
- const createComponent = ({ methods } = {}, data) => {
- wrapper = shallowMount(AlertsSettingsForm, {
- data() {
- return { ...data };
- },
- provide: {
- ...defaultAlertSettingsConfig,
- },
- methods,
- });
- };
-
- const findSelect = () => wrapper.find('[data-testid="alert-settings-select"]');
- const findJsonInput = () => wrapper.find('#alert-json');
- const findUrl = () => wrapper.find('#url');
- const findAuthorizationKey = () => wrapper.find('#authorization-key');
- const findApiUrl = () => wrapper.find('#api-url');
-
- beforeEach(() => {
- setFixtures(`
- <div>
- <span class="js-service-active-status fa fa-circle" data-value="true"></span>
- <span class="js-service-active-status fa fa-power-off" data-value="false"></span>
- </div>`);
- });
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- wrapper = null;
- }
- });
-
- describe('with default values', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders the initial template', () => {
- expect(wrapper.html()).toMatchSnapshot();
- });
- });
-
- describe('reset key', () => {
- it('triggers resetKey method', () => {
- const resetKey = jest.fn();
- const methods = { resetKey };
- createComponent({ methods });
-
- wrapper.find(GlModal).vm.$emit('ok');
-
- expect(resetKey).toHaveBeenCalled();
- });
-
- it('updates the authorization key on success', () => {
- createComponent(
- {},
- {
- token: 'newToken',
- },
- );
-
- expect(findAuthorizationKey().attributes('value')).toBe('newToken');
- });
-
- it('shows a alert message on error', () => {
- service.updateGenericKey.mockRejectedValueOnce({});
-
- createComponent();
-
- return wrapper.vm.resetKey().then(() => {
- expect(wrapper.find(GlAlert).exists()).toBe(true);
- });
- });
- });
-
- describe('activate toggle', () => {
- it('triggers toggleActivated method', () => {
- const toggleService = jest.fn();
- const methods = { toggleService };
- createComponent({ methods });
-
- wrapper.find(ToggleButton).vm.$emit('change', true);
- expect(toggleService).toHaveBeenCalled();
- });
-
- describe('error is encountered', () => {
- it('restores previous value', () => {
- service.updateGenericKey.mockRejectedValueOnce({});
- createComponent();
- return wrapper.vm.resetKey().then(() => {
- expect(wrapper.find(ToggleButton).props('value')).toBe(false);
- });
- });
- });
- });
-
- describe('prometheus is active', () => {
- beforeEach(() => {
- createComponent(
- {},
- {
- selectedIntegration: 'PROMETHEUS',
- },
- );
- });
-
- it('renders a valid "select"', () => {
- expect(findSelect().exists()).toBe(true);
- });
-
- it('shows the API URL input', () => {
- expect(findApiUrl().exists()).toBe(true);
- });
-
- it('shows the correct default API URL', () => {
- expect(findUrl().attributes('value')).toBe(defaultAlertSettingsConfig.prometheus.url);
- });
- });
-
- describe('Opsgenie is active', () => {
- beforeEach(() => {
- createComponent(
- {},
- {
- selectedIntegration: 'OPSGENIE',
- },
- );
- });
-
- it('shows a input for the Opsgenie target URL', () => {
- expect(findApiUrl().exists()).toBe(true);
- });
- });
-
- describe('trigger test alert', () => {
- beforeEach(() => {
- createComponent({});
- });
-
- it('should enable the JSON input', () => {
- expect(findJsonInput().exists()).toBe(true);
- expect(findJsonInput().props('value')).toBe(null);
- });
-
- it('should validate JSON input', async () => {
- createComponent(true, {
- testAlertJson: '{ "value": "test" }',
- });
-
- findJsonInput().vm.$emit('change');
-
- await wrapper.vm.$nextTick();
-
- expect(findJsonInput().attributes('state')).toBe('true');
- });
-
- describe('alert service is toggled', () => {
- describe('error handling', () => {
- const toggleService = true;
-
- it('should show generic error', async () => {
- service.updateGenericActive.mockRejectedValueOnce({});
-
- createComponent();
-
- await wrapper.vm.toggleActivated(toggleService);
- expect(wrapper.vm.active).toBe(false);
- expect(wrapper.find(GlAlert).attributes('variant')).toBe('danger');
- expect(wrapper.find(GlAlert).text()).toBe(i18n.errorMsg);
- });
-
- it('should show first field specific error when available', async () => {
- const err1 = "can't be blank";
- const err2 = 'is not a valid URL';
- const key = 'api_url';
- service.updateGenericActive.mockRejectedValueOnce({
- response: { data: { errors: { [key]: [err1, err2] } } },
- });
-
- createComponent();
-
- await wrapper.vm.toggleActivated(toggleService);
-
- expect(wrapper.find(GlAlert).text()).toContain(i18n.errorMsg);
- expect(wrapper.find(GlAlert).text()).toContain(`${key} ${err1}`);
- });
- });
- });
- });
-});
diff --git a/spec/frontend/alerts_settings/alerts_settings_form_new_spec.js b/spec/frontend/alerts_settings/alerts_settings_form_spec.js
index fbd482b1906..428c6f93444 100644
--- a/spec/frontend/alerts_settings/alerts_settings_form_new_spec.js
+++ b/spec/frontend/alerts_settings/alerts_settings_form_spec.js
@@ -8,7 +8,7 @@ import {
GlFormTextarea,
} from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
-import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form_new.vue';
+import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
import { defaultAlertSettingsConfig } from './util';
import { typeSet } from '~/alerts_settings/constants';
@@ -93,16 +93,28 @@ describe('AlertsSettingsFormNew', () => {
).toBe(true);
});
- it('disabled the dropdown and shows help text when multi integrations are not supported', async () => {
+ it('disables the dropdown and shows help text when multi integrations are not supported', async () => {
createComponent({ props: { canAddIntegration: false } });
expect(findSelect().attributes('disabled')).toBe('disabled');
expect(findMultiSupportText().exists()).toBe(true);
});
+
+ it('disabled the name input when the selected value is prometheus', async () => {
+ createComponent();
+ const options = findSelect().findAll('option');
+ await options.at(2).setSelected();
+
+ expect(
+ findFormFields()
+ .at(0)
+ .attributes('disabled'),
+ ).toBe('disabled');
+ });
});
describe('submitting integration form', () => {
it('allows for create-new-integration with the correct form values for HTTP', async () => {
- createComponent({});
+ createComponent();
const options = findSelect().findAll('option');
await options.at(1).setSelected();
@@ -128,7 +140,7 @@ describe('AlertsSettingsFormNew', () => {
});
it('allows for create-new-integration with the correct form values for PROMETHEUS', async () => {
- createComponent({});
+ createComponent();
const options = findSelect().findAll('option');
await options.at(2).setSelected();
diff --git a/spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js b/spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js
index 7384cf9a095..fe187d9e8f9 100644
--- a/spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js
+++ b/spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js
@@ -1,12 +1,13 @@
import VueApollo from 'vue-apollo';
import { mount, createLocalVue } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
+import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
+import axios from '~/lib/utils/axios_utils';
import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
-import AlertsSettingsFormOld from '~/alerts_settings/components/alerts_settings_form_old.vue';
-import AlertsSettingsFormNew from '~/alerts_settings/components/alerts_settings_form_new.vue';
+import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue';
import getIntegrationsQuery from '~/alerts_settings/graphql/queries/get_integrations.query.graphql';
import createHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
@@ -75,7 +76,6 @@ describe('AlertsSettingsWrapper', () => {
},
provide: {
...defaultAlertSettingsConfig,
- glFeatures: { httpIntegrationsList: false },
...provide,
},
mocks: {
@@ -110,39 +110,25 @@ describe('AlertsSettingsWrapper', () => {
apolloProvider: fakeApollo,
provide: {
...defaultAlertSettingsConfig,
- glFeatures: { httpIntegrationsList: true },
},
});
}
afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- wrapper = null;
- }
+ wrapper.destroy();
+ wrapper = null;
});
- describe('with httpIntegrationsList feature flag disabled', () => {
- it('renders data driven alerts integrations list and old form by default', () => {
- createComponent();
- expect(wrapper.find(IntegrationsList).exists()).toBe(true);
- expect(wrapper.find(AlertsSettingsFormOld).exists()).toBe(true);
- expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(false);
- });
- });
-
- describe('with httpIntegrationsList feature flag enabled', () => {
+ describe('rendered via default permissions', () => {
it('renders the GraphQL alerts integrations list and new form', () => {
- createComponent({ provide: { glFeatures: { httpIntegrationsList: true } } });
+ createComponent();
expect(wrapper.find(IntegrationsList).exists()).toBe(true);
- expect(wrapper.find(AlertsSettingsFormOld).exists()).toBe(false);
- expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(true);
+ expect(wrapper.find(AlertsSettingsForm).exists()).toBe(true);
});
it('uses a loading state inside the IntegrationsList table', () => {
createComponent({
data: { integrations: {} },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: true,
});
expect(wrapper.find(IntegrationsList).exists()).toBe(true);
@@ -152,7 +138,6 @@ describe('AlertsSettingsWrapper', () => {
it('renders the IntegrationsList table using the API data', () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
expect(findLoader().exists()).toBe(false);
@@ -162,14 +147,13 @@ describe('AlertsSettingsWrapper', () => {
it('calls `$apollo.mutate` with `createHttpIntegrationMutation`', () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { createHttpIntegrationMutation: { integration: { id: '1' } } },
});
- wrapper.find(AlertsSettingsFormNew).vm.$emit('create-new-integration', {
+ wrapper.find(AlertsSettingsForm).vm.$emit('create-new-integration', {
type: typeSet.http,
variables: createHttpVariables,
});
@@ -185,14 +169,13 @@ describe('AlertsSettingsWrapper', () => {
it('calls `$apollo.mutate` with `updateHttpIntegrationMutation`', () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { updateHttpIntegrationMutation: { integration: { id: '1' } } },
});
- wrapper.find(AlertsSettingsFormNew).vm.$emit('update-integration', {
+ wrapper.find(AlertsSettingsForm).vm.$emit('update-integration', {
type: typeSet.http,
variables: updateHttpVariables,
});
@@ -206,14 +189,13 @@ describe('AlertsSettingsWrapper', () => {
it('calls `$apollo.mutate` with `resetHttpTokenMutation`', () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { resetHttpTokenMutation: { integration: { id: '1' } } },
});
- wrapper.find(AlertsSettingsFormNew).vm.$emit('reset-token', {
+ wrapper.find(AlertsSettingsForm).vm.$emit('reset-token', {
type: typeSet.http,
variables: { id: ID },
});
@@ -229,14 +211,13 @@ describe('AlertsSettingsWrapper', () => {
it('calls `$apollo.mutate` with `createPrometheusIntegrationMutation`', () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { createPrometheusIntegrationMutation: { integration: { id: '2' } } },
});
- wrapper.find(AlertsSettingsFormNew).vm.$emit('create-new-integration', {
+ wrapper.find(AlertsSettingsForm).vm.$emit('create-new-integration', {
type: typeSet.prometheus,
variables: createPrometheusVariables,
});
@@ -252,14 +233,13 @@ describe('AlertsSettingsWrapper', () => {
it('calls `$apollo.mutate` with `updatePrometheusIntegrationMutation`', () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { updatePrometheusIntegrationMutation: { integration: { id: '2' } } },
});
- wrapper.find(AlertsSettingsFormNew).vm.$emit('update-integration', {
+ wrapper.find(AlertsSettingsForm).vm.$emit('update-integration', {
type: typeSet.prometheus,
variables: updatePrometheusVariables,
});
@@ -273,14 +253,13 @@ describe('AlertsSettingsWrapper', () => {
it('calls `$apollo.mutate` with `resetPrometheusTokenMutation`', () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { resetPrometheusTokenMutation: { integration: { id: '1' } } },
});
- wrapper.find(AlertsSettingsFormNew).vm.$emit('reset-token', {
+ wrapper.find(AlertsSettingsForm).vm.$emit('reset-token', {
type: typeSet.prometheus,
variables: { id: ID },
});
@@ -296,12 +275,11 @@ describe('AlertsSettingsWrapper', () => {
it('shows an error alert when integration creation fails ', async () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(ADD_INTEGRATION_ERROR);
- wrapper.find(AlertsSettingsFormNew).vm.$emit('create-new-integration', {});
+ wrapper.find(AlertsSettingsForm).vm.$emit('create-new-integration', {});
await waitForPromises();
@@ -311,13 +289,12 @@ describe('AlertsSettingsWrapper', () => {
it('shows an error alert when integration token reset fails ', async () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(RESET_INTEGRATION_TOKEN_ERROR);
- wrapper.find(AlertsSettingsFormNew).vm.$emit('reset-token', {});
+ wrapper.find(AlertsSettingsForm).vm.$emit('reset-token', {});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: RESET_INTEGRATION_TOKEN_ERROR });
@@ -326,30 +303,30 @@ describe('AlertsSettingsWrapper', () => {
it('shows an error alert when integration update fails ', async () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(errorMsg);
- wrapper.find(AlertsSettingsFormNew).vm.$emit('update-integration', {});
+ wrapper.find(AlertsSettingsForm).vm.$emit('update-integration', {});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: UPDATE_INTEGRATION_ERROR });
});
it('shows an error alert when integration test payload fails ', async () => {
+ const mock = new AxiosMockAdapter(axios);
+ mock.onPost(/(.*)/).replyOnce(403);
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
- wrapper.find(AlertsSettingsFormNew).vm.$emit('test-payload-failure');
-
- await waitForPromises();
- expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
- expect(createFlash).toHaveBeenCalledTimes(1);
+ return wrapper.vm.validateAlertPayload({ endpoint: '', data: '', token: '' }).then(() => {
+ expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ mock.restore();
+ });
});
});
@@ -405,7 +382,7 @@ describe('AlertsSettingsWrapper', () => {
it.each([true, false])('it shows/hides the alert when opsgenie is %s', active => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
- provide: { glFeatures: { httpIntegrationsList: true }, opsgenie: { active } },
+ provide: { opsgenie: { active } },
loading: false,
});
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 724d33922a1..37630c15b89 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -1254,6 +1254,46 @@ describe('Api', () => {
});
});
+ describe('trackRedisCounterEvent', () => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/usage_data/increment_counter`;
+
+ const event = 'dummy_event';
+ const postData = { event };
+ const headers = {
+ 'Content-Type': 'application/json',
+ };
+
+ describe('when usage data increment counter is called with feature flag disabled', () => {
+ beforeEach(() => {
+ gon.features = { ...gon.features, usageDataApi: false };
+ });
+
+ it('returns null', () => {
+ jest.spyOn(axios, 'post');
+ mock.onPost(expectedUrl).replyOnce(httpStatus.OK, true);
+
+ expect(axios.post).toHaveBeenCalledTimes(0);
+ expect(Api.trackRedisCounterEvent(event)).toEqual(null);
+ });
+ });
+
+ describe('when usage data increment counter is called', () => {
+ beforeEach(() => {
+ gon.features = { ...gon.features, usageDataApi: true };
+ });
+
+ it('resolves the Promise', () => {
+ jest.spyOn(axios, 'post');
+ mock.onPost(expectedUrl, { event }).replyOnce(httpStatus.OK, true);
+
+ return Api.trackRedisCounterEvent(event).then(({ data }) => {
+ expect(data).toEqual(true);
+ expect(axios.post).toHaveBeenCalledWith(expectedUrl, postData, { headers });
+ });
+ });
+ });
+ });
+
describe('trackRedisHllUserEvent', () => {
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/usage_data/increment_unique_users`;
diff --git a/spec/frontend/authentication/two_factor_auth/components/recovery_codes_spec.js b/spec/frontend/authentication/two_factor_auth/components/recovery_codes_spec.js
new file mode 100644
index 00000000000..025e605b920
--- /dev/null
+++ b/spec/frontend/authentication/two_factor_auth/components/recovery_codes_spec.js
@@ -0,0 +1,242 @@
+import { mount } from '@vue/test-utils';
+import { GlAlert, GlButton } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { within } from '@testing-library/dom';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import Tracking from '~/tracking';
+import RecoveryCodes, {
+ i18n,
+} from '~/authentication/two_factor_auth/components/recovery_codes.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import {
+ RECOVERY_CODE_DOWNLOAD_FILENAME,
+ COPY_KEYBOARD_SHORTCUT,
+} from '~/authentication/two_factor_auth/constants';
+import { codes, codesFormattedString, codesDownloadHref, profileAccountPath } from '../mock_data';
+
+describe('RecoveryCodes', () => {
+ let wrapper;
+
+ const createComponent = (options = {}) => {
+ wrapper = extendedWrapper(
+ mount(RecoveryCodes, {
+ propsData: {
+ codes,
+ profileAccountPath,
+ ...(options?.propsData || {}),
+ },
+ ...options,
+ }),
+ );
+ };
+
+ const queryByText = (text, options) => within(wrapper.element).queryByText(text, options);
+ const findAlert = () => wrapper.find(GlAlert);
+ const findRecoveryCodes = () => wrapper.findByTestId('recovery-codes');
+ const findCopyButton = () => wrapper.find(ClipboardButton);
+ const findButtonByText = text =>
+ wrapper.findAll(GlButton).wrappers.find(buttonWrapper => buttonWrapper.text() === text);
+ const findDownloadButton = () => findButtonByText('Download codes');
+ const findPrintButton = () => findButtonByText('Print codes');
+ const findProceedButton = () => findButtonByText('Proceed');
+ const manuallyCopyRecoveryCodes = () =>
+ wrapper.vm.$options.mousetrap.trigger(COPY_KEYBOARD_SHORTCUT);
+
+ beforeEach(() => {
+ jest.spyOn(Tracking, 'event');
+ createComponent();
+ });
+
+ it('renders title', () => {
+ expect(queryByText(i18n.pageTitle)).toEqual(expect.any(HTMLElement));
+ });
+
+ it('renders alert', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(i18n.alertTitle);
+ });
+
+ it('renders codes', () => {
+ const recoveryCodes = findRecoveryCodes().text();
+
+ codes.forEach(code => {
+ expect(recoveryCodes).toContain(code);
+ });
+ });
+
+ describe('"Proceed" button', () => {
+ it('renders button as disabled', () => {
+ const proceedButton = findProceedButton();
+
+ expect(proceedButton.exists()).toBe(true);
+ expect(proceedButton.props('disabled')).toBe(true);
+ expect(proceedButton.attributes()).toMatchObject({
+ title: i18n.proceedButton,
+ href: profileAccountPath,
+ });
+ });
+
+ it('fires Snowplow event', () => {
+ expect(findProceedButton().attributes()).toMatchObject({
+ 'data-track-event': 'click_button',
+ 'data-track-label': '2fa_recovery_codes_proceed_button',
+ });
+ });
+ });
+
+ describe('"Copy codes" button', () => {
+ it('renders button', () => {
+ const copyButton = findCopyButton();
+
+ expect(copyButton.exists()).toBe(true);
+ expect(copyButton.text()).toBe(i18n.copyButton);
+ expect(copyButton.props()).toMatchObject({
+ title: i18n.copyButton,
+ text: codesFormattedString,
+ });
+ });
+
+ describe('when button is clicked', () => {
+ beforeEach(async () => {
+ findCopyButton().trigger('click');
+
+ await nextTick();
+ });
+
+ it('enables "Proceed" button', () => {
+ expect(findProceedButton().props('disabled')).toBe(false);
+ });
+
+ it('fires Snowplow event', () => {
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
+ label: '2fa_recovery_codes_copy_button',
+ });
+ });
+ });
+ });
+
+ describe('"Download codes" button', () => {
+ it('renders button', () => {
+ const downloadButton = findDownloadButton();
+
+ expect(downloadButton.exists()).toBe(true);
+ expect(downloadButton.attributes()).toMatchObject({
+ title: i18n.downloadButton,
+ download: RECOVERY_CODE_DOWNLOAD_FILENAME,
+ href: codesDownloadHref,
+ });
+ });
+
+ describe('when button is clicked', () => {
+ beforeEach(async () => {
+ const downloadButton = findDownloadButton();
+ // jsdom does not support navigating.
+ // Since we are clicking an anchor tag there is no way to mock this
+ // and we are forced to instead remove the `href` attribute.
+ // More info: https://github.com/jsdom/jsdom/issues/2112#issuecomment-663672587
+ downloadButton.element.removeAttribute('href');
+ downloadButton.trigger('click');
+
+ await nextTick();
+ });
+
+ it('enables "Proceed" button', () => {
+ expect(findProceedButton().props('disabled')).toBe(false);
+ });
+
+ it('fires Snowplow event', () => {
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
+ label: '2fa_recovery_codes_download_button',
+ });
+ });
+ });
+ });
+
+ describe('"Print codes" button', () => {
+ it('renders button', () => {
+ const printButton = findPrintButton();
+
+ expect(printButton.exists()).toBe(true);
+ expect(printButton.attributes()).toMatchObject({
+ title: i18n.printButton,
+ });
+ });
+
+ describe('when button is clicked', () => {
+ beforeEach(async () => {
+ window.print = jest.fn();
+
+ findPrintButton().trigger('click');
+
+ await nextTick();
+ });
+
+ it('enables "Proceed" button and opens print dialog', () => {
+ expect(findProceedButton().props('disabled')).toBe(false);
+ expect(window.print).toHaveBeenCalled();
+ });
+
+ it('fires Snowplow event', () => {
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
+ label: '2fa_recovery_codes_print_button',
+ });
+ });
+ });
+ });
+
+ describe('when codes are manually copied', () => {
+ describe('when selected text is the recovery codes', () => {
+ beforeEach(async () => {
+ jest.spyOn(window, 'getSelection').mockImplementation(() => ({
+ toString: jest.fn(() => codesFormattedString),
+ }));
+
+ manuallyCopyRecoveryCodes();
+
+ await nextTick();
+ });
+
+ it('enables "Proceed" button', () => {
+ expect(findProceedButton().props('disabled')).toBe(false);
+ });
+
+ it('fires Snowplow event', () => {
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, 'copy_keyboard_shortcut', {
+ label: '2fa_recovery_codes_manual_copy',
+ });
+ });
+ });
+
+ describe('when selected text includes the recovery codes', () => {
+ beforeEach(() => {
+ jest.spyOn(window, 'getSelection').mockImplementation(() => ({
+ toString: jest.fn(() => `foo bar ${codesFormattedString}`),
+ }));
+ });
+
+ it('enables "Proceed" button', async () => {
+ manuallyCopyRecoveryCodes();
+
+ await nextTick();
+
+ expect(findProceedButton().props('disabled')).toBe(false);
+ });
+ });
+
+ describe('when selected text does not include the recovery codes', () => {
+ beforeEach(() => {
+ jest.spyOn(window, 'getSelection').mockImplementation(() => ({
+ toString: jest.fn(() => 'foo bar'),
+ }));
+ });
+
+ it('keeps "Proceed" button disabled', async () => {
+ manuallyCopyRecoveryCodes();
+
+ await nextTick();
+
+ expect(findProceedButton().props('disabled')).toBe(true);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/authentication/two_factor_auth/index_spec.js b/spec/frontend/authentication/two_factor_auth/index_spec.js
new file mode 100644
index 00000000000..b181170b0a1
--- /dev/null
+++ b/spec/frontend/authentication/two_factor_auth/index_spec.js
@@ -0,0 +1,80 @@
+import { createWrapper } from '@vue/test-utils';
+import { getByTestId, fireEvent } from '@testing-library/dom';
+import * as urlUtils from '~/lib/utils/url_utility';
+import { initRecoveryCodes, initClose2faSuccessMessage } from '~/authentication/two_factor_auth';
+import RecoveryCodes from '~/authentication/two_factor_auth/components/recovery_codes.vue';
+import { codesJsonString, codes, profileAccountPath } from './mock_data';
+
+describe('initRecoveryCodes', () => {
+ let el;
+ let wrapper;
+
+ const findRecoveryCodesComponent = () => wrapper.find(RecoveryCodes);
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ el.setAttribute('class', 'js-2fa-recovery-codes');
+ el.setAttribute('data-codes', codesJsonString);
+ el.setAttribute('data-profile-account-path', profileAccountPath);
+ document.body.appendChild(el);
+
+ wrapper = createWrapper(initRecoveryCodes());
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ });
+
+ it('parses `data-codes` and passes to `RecoveryCodes` as `codes` prop', () => {
+ expect(findRecoveryCodesComponent().props('codes')).toEqual(codes);
+ });
+
+ it('parses `data-profile-account-path` and passes to `RecoveryCodes` as `profileAccountPath` prop', () => {
+ expect(findRecoveryCodesComponent().props('profileAccountPath')).toEqual(profileAccountPath);
+ });
+});
+
+describe('initClose2faSuccessMessage', () => {
+ beforeEach(() => {
+ document.body.innerHTML = `
+ <button
+ data-testid="close-2fa-enabled-success-alert"
+ class="js-close-2fa-enabled-success-alert"
+ >
+ </button>
+ `;
+
+ initClose2faSuccessMessage();
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ });
+
+ describe('when alert is closed', () => {
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL(
+ 'https://localhost/-/profile/account?two_factor_auth_enabled_successfully=true',
+ );
+
+ document.title = 'foo bar';
+
+ urlUtils.updateHistory = jest.fn();
+ });
+
+ afterEach(() => {
+ document.title = '';
+ });
+
+ it('removes `two_factor_auth_enabled_successfully` query param', () => {
+ fireEvent.click(getByTestId(document.body, 'close-2fa-enabled-success-alert'));
+
+ expect(urlUtils.updateHistory).toHaveBeenCalledWith({
+ url: 'https://localhost/-/profile/account',
+ title: 'foo bar',
+ replace: true,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/authentication/two_factor_auth/mock_data.js b/spec/frontend/authentication/two_factor_auth/mock_data.js
new file mode 100644
index 00000000000..7b2a1764abd
--- /dev/null
+++ b/spec/frontend/authentication/two_factor_auth/mock_data.js
@@ -0,0 +1,31 @@
+export const codes = [
+ 'e8471c403a6a84c0',
+ 'b1b92de21c68f08e',
+ 'd7689f332cd8cd73',
+ '05b706accfa95cfa',
+ 'b0a2b45ea956c1d2',
+ '599dc672d18d5161',
+ 'e14e9f4adf4b8bf2',
+ '1013007a75efeeec',
+ '26bd057c4c696a4f',
+ '1c46fba5a4275ef4',
+];
+
+export const codesJsonString =
+ '["e8471c403a6a84c0","b1b92de21c68f08e","d7689f332cd8cd73","05b706accfa95cfa","b0a2b45ea956c1d2","599dc672d18d5161","e14e9f4adf4b8bf2","1013007a75efeeec","26bd057c4c696a4f","1c46fba5a4275ef4"]';
+
+export const codesFormattedString = `e8471c403a6a84c0
+b1b92de21c68f08e
+d7689f332cd8cd73
+05b706accfa95cfa
+b0a2b45ea956c1d2
+599dc672d18d5161
+e14e9f4adf4b8bf2
+1013007a75efeeec
+26bd057c4c696a4f
+1c46fba5a4275ef4`;
+
+export const codesDownloadHref =
+ 'data:text/plain;charset=utf-8,e8471c403a6a84c0%0Ab1b92de21c68f08e%0Ad7689f332cd8cd73%0A05b706accfa95cfa%0Ab0a2b45ea956c1d2%0A599dc672d18d5161%0Ae14e9f4adf4b8bf2%0A1013007a75efeeec%0A26bd057c4c696a4f%0A1c46fba5a4275ef4';
+
+export const profileAccountPath = '/-/profile/account';
diff --git a/spec/frontend/blob/components/mock_data.js b/spec/frontend/blob/components/mock_data.js
index 8cfcec2693c..95789ca13cb 100644
--- a/spec/frontend/blob/components/mock_data.js
+++ b/spec/frontend/blob/components/mock_data.js
@@ -55,5 +55,3 @@ export const SimpleBlobContentMock = {
path: 'foo.js',
plainData: 'Plain',
};
-
-export default {};
diff --git a/spec/frontend/blob/viewer/index_spec.js b/spec/frontend/blob/viewer/index_spec.js
index 69ec22b1f94..a4b4044f5f9 100644
--- a/spec/frontend/blob/viewer/index_spec.js
+++ b/spec/frontend/blob/viewer/index_spec.js
@@ -154,7 +154,7 @@ describe('Blob viewer', () => {
blob.switchToViewer('simple');
- expect(simpleBtn.classList.contains('active')).toBeTruthy();
+ expect(simpleBtn.classList.contains('selected')).toBeTruthy();
expect(simpleBtn.blur).toHaveBeenCalled();
});
diff --git a/spec/frontend/blob_edit/edit_blob_spec.js b/spec/frontend/blob_edit/edit_blob_spec.js
index ac8b916e448..9637ea09a3a 100644
--- a/spec/frontend/blob_edit/edit_blob_spec.js
+++ b/spec/frontend/blob_edit/edit_blob_spec.js
@@ -1,25 +1,35 @@
import waitForPromises from 'helpers/wait_for_promises';
import EditBlob from '~/blob_edit/edit_blob';
import EditorLite from '~/editor/editor_lite';
-import MarkdownExtension from '~/editor/editor_markdown_ext';
-import FileTemplateExtension from '~/editor/editor_file_template_ext';
+import { EditorMarkdownExtension } from '~/editor/editor_markdown_ext';
+import { FileTemplateExtension } from '~/editor/editor_file_template_ext';
jest.mock('~/editor/editor_lite');
jest.mock('~/editor/editor_markdown_ext');
+jest.mock('~/editor/editor_file_template_ext');
describe('Blob Editing', () => {
const useMock = jest.fn();
const mockInstance = {
use: useMock,
- getValue: jest.fn(),
+ setValue: jest.fn(),
+ getValue: jest.fn().mockReturnValue('test value'),
focus: jest.fn(),
};
beforeEach(() => {
- setFixtures(
- `<div class="js-edit-blob-form"><div id="file_path"></div><div id="editor"></div><input id="file-content"></div>`,
- );
+ setFixtures(`
+ <form class="js-edit-blob-form">
+ <div id="file_path"></div>
+ <div id="editor"></div>
+ <textarea id="file-content"></textarea>
+ </form>
+ `);
jest.spyOn(EditorLite.prototype, 'createInstance').mockReturnValue(mockInstance);
});
+ afterEach(() => {
+ EditorMarkdownExtension.mockClear();
+ FileTemplateExtension.mockClear();
+ });
const editorInst = isMarkdown => {
return new EditBlob({
@@ -34,20 +44,31 @@ describe('Blob Editing', () => {
it('loads FileTemplateExtension by default', async () => {
await initEditor();
- expect(useMock).toHaveBeenCalledWith(FileTemplateExtension);
+ expect(FileTemplateExtension).toHaveBeenCalledTimes(1);
});
describe('Markdown', () => {
it('does not load MarkdownExtension by default', async () => {
await initEditor();
- expect(useMock).not.toHaveBeenCalledWith(MarkdownExtension);
+ expect(EditorMarkdownExtension).not.toHaveBeenCalled();
});
it('loads MarkdownExtension only for the markdown files', async () => {
await initEditor(true);
expect(useMock).toHaveBeenCalledTimes(2);
- expect(useMock).toHaveBeenNthCalledWith(1, FileTemplateExtension);
- expect(useMock).toHaveBeenNthCalledWith(2, MarkdownExtension);
+ expect(FileTemplateExtension).toHaveBeenCalledTimes(1);
+ expect(EditorMarkdownExtension).toHaveBeenCalledTimes(1);
});
});
+
+ it('adds trailing newline to the blob content on submit', async () => {
+ const form = document.querySelector('.js-edit-blob-form');
+ const fileContentEl = document.getElementById('file-content');
+
+ await initEditor();
+
+ form.dispatchEvent(new Event('submit'));
+
+ expect(fileContentEl.value).toBe('test value\n');
+ });
});
diff --git a/spec/frontend/boards/board_list_new_spec.js b/spec/frontend/boards/board_list_new_spec.js
index 55516e3fd56..96b03ed927e 100644
--- a/spec/frontend/boards/board_list_new_spec.js
+++ b/spec/frontend/boards/board_list_new_spec.js
@@ -1,15 +1,11 @@
-/* global List */
-/* global ListIssue */
-
import Vuex from 'vuex';
import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
import { createLocalVue, mount } from '@vue/test-utils';
import eventHub from '~/boards/eventhub';
import BoardList from '~/boards/components/board_list_new.vue';
import BoardCard from '~/boards/components/board_card.vue';
-import '~/boards/models/issue';
import '~/boards/models/list';
-import { listObj, mockIssuesByListId, issues } from './mock_data';
+import { mockList, mockIssuesByListId, issues, mockIssues } from './mock_data';
import defaultState from '~/boards/stores/state';
const localVue = createLocalVue();
@@ -46,13 +42,11 @@ const createComponent = ({
...state,
});
- const list = new List({
- ...listObj,
- id: 'gid://gitlab/List/1',
+ const list = {
+ ...mockList,
...listProps,
- doNotFetchIssues: true,
- });
- const issue = new ListIssue({
+ };
+ const issue = {
title: 'Testing',
id: 1,
iid: 1,
@@ -60,9 +54,9 @@ const createComponent = ({
labels: [],
assignees: [],
...listIssueProps,
- });
- if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesSize')) {
- list.issuesSize = 1;
+ };
+ if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesCount')) {
+ list.issuesCount = 1;
}
const component = mount(BoardList, {
@@ -71,6 +65,7 @@ const createComponent = ({
disabled: false,
list,
issues: [issue],
+ canAdminList: true,
...componentProps,
},
store,
@@ -87,17 +82,19 @@ const createComponent = ({
describe('Board list component', () => {
let wrapper;
+ const findByTestId = testId => wrapper.find(`[data-testid="${testId}"]`);
useFakeRequestAnimationFrame();
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
describe('When Expanded', () => {
beforeEach(() => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders component', () => {
expect(wrapper.find('.board-list-component').exists()).toBe(true);
});
@@ -107,7 +104,7 @@ describe('Board list component', () => {
state: { listsFlags: { 'gid://gitlab/List/1': { isLoading: true } } },
});
- expect(wrapper.find('[data-testid="board_list_loading"').exists()).toBe(true);
+ expect(findByTestId('board_list_loading').exists()).toBe(true);
});
it('renders issues', () => {
@@ -157,7 +154,7 @@ describe('Board list component', () => {
it('shows how many more issues to load', async () => {
wrapper.vm.showCount = true;
- wrapper.setProps({ list: { issuesSize: 20 } });
+ wrapper.setProps({ list: { issuesCount: 20 } });
await wrapper.vm.$nextTick();
expect(wrapper.find('.board-list-count').text()).toBe('Showing 1 of 20 issues');
@@ -167,30 +164,30 @@ describe('Board list component', () => {
describe('load more issues', () => {
beforeEach(() => {
wrapper = createComponent({
- listProps: { issuesSize: 25 },
+ listProps: { issuesCount: 25 },
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('loads more issues after scrolling', () => {
- wrapper.vm.$refs.list.dispatchEvent(new Event('scroll'));
+ wrapper.vm.listRef.dispatchEvent(new Event('scroll'));
expect(actions.fetchIssuesForList).toHaveBeenCalled();
});
it('does not load issues if already loading', () => {
- wrapper.vm.$refs.list.dispatchEvent(new Event('scroll'));
- wrapper.vm.$refs.list.dispatchEvent(new Event('scroll'));
+ wrapper = createComponent({
+ state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } },
+ });
+ wrapper.vm.listRef.dispatchEvent(new Event('scroll'));
- expect(actions.fetchIssuesForList).toHaveBeenCalledTimes(1);
+ expect(actions.fetchIssuesForList).not.toHaveBeenCalled();
});
it('shows loading more spinner', async () => {
+ wrapper = createComponent({
+ state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } },
+ });
wrapper.vm.showCount = true;
- wrapper.vm.list.loadingMore = true;
await wrapper.vm.$nextTick();
expect(wrapper.find('.board-list-count .gl-spinner').exists()).toBe(true);
@@ -200,17 +197,13 @@ describe('Board list component', () => {
describe('max issue count warning', () => {
beforeEach(() => {
wrapper = createComponent({
- listProps: { issuesSize: 50 },
+ listProps: { issuesCount: 50 },
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when issue count exceeds max issue count', () => {
it('sets background to bg-danger-100', async () => {
- wrapper.setProps({ list: { issuesSize: 4, maxIssueCount: 3 } });
+ wrapper.setProps({ list: { issuesCount: 4, maxIssueCount: 3 } });
await wrapper.vm.$nextTick();
expect(wrapper.find('.bg-danger-100').exists()).toBe(true);
@@ -219,7 +212,7 @@ describe('Board list component', () => {
describe('when list issue count does NOT exceed list max issue count', () => {
it('does not sets background to bg-danger-100', () => {
- wrapper.setProps({ list: { issuesSize: 2, maxIssueCount: 3 } });
+ wrapper.setProps({ list: { issuesCount: 2, maxIssueCount: 3 } });
expect(wrapper.find('.bg-danger-100').exists()).toBe(false);
});
@@ -233,4 +226,43 @@ describe('Board list component', () => {
});
});
});
+
+ describe('drag & drop issue', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ describe('handleDragOnStart', () => {
+ it('adds a class `is-dragging` to document body', () => {
+ expect(document.body.classList.contains('is-dragging')).toBe(false);
+
+ findByTestId('tree-root-wrapper').vm.$emit('start');
+
+ expect(document.body.classList.contains('is-dragging')).toBe(true);
+ });
+ });
+
+ describe('handleDragOnEnd', () => {
+ it('removes class `is-dragging` from document body', () => {
+ jest.spyOn(wrapper.vm, 'moveIssue').mockImplementation(() => {});
+ document.body.classList.add('is-dragging');
+
+ findByTestId('tree-root-wrapper').vm.$emit('end', {
+ oldIndex: 1,
+ newIndex: 0,
+ item: {
+ dataset: {
+ issueId: mockIssues[0].id,
+ issueIid: mockIssues[0].iid,
+ issuePath: mockIssues[0].referencePath,
+ },
+ },
+ to: { children: [], dataset: { listId: 'gid://gitlab/List/1' } },
+ from: { dataset: { listId: 'gid://gitlab/List/2' } },
+ });
+
+ expect(document.body.classList.contains('is-dragging')).toBe(false);
+ });
+ });
+ });
});
diff --git a/spec/frontend/boards/boards_store_spec.js b/spec/frontend/boards/boards_store_spec.js
index e7c1cf79fdc..c89f6d22ef2 100644
--- a/spec/frontend/boards/boards_store_spec.js
+++ b/spec/frontend/boards/boards_store_spec.js
@@ -1,7 +1,7 @@
import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
-import boardsStore, { gqlClient } from '~/boards/stores/boards_store';
+import boardsStore from '~/boards/stores/boards_store';
import eventHub from '~/boards/eventhub';
import { listObj, listObjDuplicate } from './mock_data';
@@ -66,23 +66,6 @@ describe('boardsStore', () => {
});
});
- describe('generateDefaultLists', () => {
- const listsEndpointGenerate = `${endpoints.listsEndpoint}/generate.json`;
-
- it('makes a request to generate default lists', () => {
- axiosMock.onPost(listsEndpointGenerate).replyOnce(200, dummyResponse);
- const expectedResponse = expect.objectContaining({ data: dummyResponse });
-
- return expect(boardsStore.generateDefaultLists()).resolves.toEqual(expectedResponse);
- });
-
- it('fails for error response', () => {
- axiosMock.onPost(listsEndpointGenerate).replyOnce(500);
-
- return expect(boardsStore.generateDefaultLists()).rejects.toThrow();
- });
- });
-
describe('createList', () => {
const entityType = 'moorhen';
const entityId = 'quack';
@@ -473,118 +456,6 @@ describe('boardsStore', () => {
});
});
- describe('createBoard', () => {
- const labelIds = ['first label', 'second label'];
- const assigneeId = 'as sign ee';
- const milestoneId = 'vegetable soup';
- const board = {
- labels: labelIds.map(id => ({ id })),
- assignee: { id: assigneeId },
- milestone: { id: milestoneId },
- };
-
- describe('for existing board', () => {
- const id = 'skate-board';
- const url = `${endpoints.boardsEndpoint}/${id}.json`;
- const expectedRequest = expect.objectContaining({
- data: JSON.stringify({
- board: {
- ...board,
- id,
- label_ids: labelIds,
- assignee_id: assigneeId,
- milestone_id: milestoneId,
- },
- }),
- });
-
- let requestSpy;
-
- beforeEach(() => {
- requestSpy = jest.fn();
- axiosMock.onPut(url).replyOnce(config => requestSpy(config));
- jest.spyOn(gqlClient, 'mutate').mockReturnValue(Promise.resolve({}));
- });
-
- it('makes a request to update the board', () => {
- requestSpy.mockReturnValue([200, dummyResponse]);
- const expectedResponse = [
- expect.objectContaining({ data: dummyResponse }),
- expect.objectContaining({}),
- ];
-
- return expect(
- boardsStore.createBoard({
- ...board,
- id,
- }),
- )
- .resolves.toEqual(expectedResponse)
- .then(() => {
- expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
- });
- });
-
- it('fails for error response', () => {
- requestSpy.mockReturnValue([500]);
-
- return expect(
- boardsStore.createBoard({
- ...board,
- id,
- }),
- )
- .rejects.toThrow()
- .then(() => {
- expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
- });
- });
- });
-
- describe('for new board', () => {
- const url = `${endpoints.boardsEndpoint}.json`;
- const expectedRequest = expect.objectContaining({
- data: JSON.stringify({
- board: {
- ...board,
- label_ids: labelIds,
- assignee_id: assigneeId,
- milestone_id: milestoneId,
- },
- }),
- });
-
- let requestSpy;
-
- beforeEach(() => {
- requestSpy = jest.fn();
- axiosMock.onPost(url).replyOnce(config => requestSpy(config));
- jest.spyOn(gqlClient, 'mutate').mockReturnValue(Promise.resolve({}));
- });
-
- it('makes a request to create a new board', () => {
- requestSpy.mockReturnValue([200, dummyResponse]);
- const expectedResponse = dummyResponse;
-
- return expect(boardsStore.createBoard(board))
- .resolves.toEqual(expectedResponse)
- .then(() => {
- expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
- });
- });
-
- it('fails for error response', () => {
- requestSpy.mockReturnValue([500]);
-
- return expect(boardsStore.createBoard(board))
- .rejects.toThrow()
- .then(() => {
- expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
- });
- });
- });
- });
-
describe('deleteBoard', () => {
const id = 'capsized';
const url = `${endpoints.boardsEndpoint}/${id}.json`;
@@ -727,24 +598,6 @@ describe('boardsStore', () => {
});
});
- it('check for blank state adding', () => {
- expect(boardsStore.shouldAddBlankState()).toBe(true);
- });
-
- it('check for blank state not adding', () => {
- boardsStore.addList(listObj);
-
- expect(boardsStore.shouldAddBlankState()).toBe(false);
- });
-
- it('check for blank state adding when closed list exist', () => {
- boardsStore.addList({
- list_type: 'closed',
- });
-
- expect(boardsStore.shouldAddBlankState()).toBe(true);
- });
-
it('removes list from state', () => {
boardsStore.addList(listObj);
diff --git a/spec/frontend/boards/components/board_assignee_dropdown_spec.js b/spec/frontend/boards/components/board_assignee_dropdown_spec.js
index e185a6d5419..bbdcc707f09 100644
--- a/spec/frontend/boards/components/board_assignee_dropdown_spec.js
+++ b/spec/frontend/boards/components/board_assignee_dropdown_spec.js
@@ -1,5 +1,11 @@
import { mount, createLocalVue } from '@vue/test-utils';
-import { GlDropdownItem, GlAvatarLink, GlAvatarLabeled, GlSearchBoxByType } from '@gitlab/ui';
+import {
+ GlDropdownItem,
+ GlAvatarLink,
+ GlAvatarLabeled,
+ GlSearchBoxByType,
+ GlLoadingIcon,
+} from '@gitlab/ui';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import VueApollo from 'vue-apollo';
import BoardAssigneeDropdown from '~/boards/components/board_assignee_dropdown.vue';
@@ -8,7 +14,7 @@ import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dro
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import store from '~/boards/stores';
import getIssueParticipants from '~/vue_shared/components/sidebar/queries/getIssueParticipants.query.graphql';
-import searchUsers from '~/boards/queries/users_search.query.graphql';
+import searchUsers from '~/boards/graphql/users_search.query.graphql';
import { participants } from '../mock_data';
const localVue = createLocalVue();
@@ -20,17 +26,18 @@ describe('BoardCardAssigneeDropdown', () => {
let fakeApollo;
let getIssueParticipantsSpy;
let getSearchUsersSpy;
+ let dispatchSpy;
const iid = '111';
const activeIssueName = 'test';
const anotherIssueName = 'hello';
- const createComponent = (search = '') => {
+ const createComponent = (search = '', loading = false) => {
wrapper = mount(BoardAssigneeDropdown, {
data() {
return {
search,
- selected: store.getters.activeIssue.assignees,
+ selected: [],
participants,
};
},
@@ -39,6 +46,15 @@ describe('BoardCardAssigneeDropdown', () => {
canUpdate: true,
rootPath: '',
},
+ mocks: {
+ $apollo: {
+ queries: {
+ participants: {
+ loading,
+ },
+ },
+ },
+ },
});
};
@@ -47,14 +63,13 @@ describe('BoardCardAssigneeDropdown', () => {
[getIssueParticipants, getIssueParticipantsSpy],
[searchUsers, getSearchUsersSpy],
]);
-
wrapper = mount(BoardAssigneeDropdown, {
localVue,
apolloProvider: fakeApollo,
data() {
return {
search,
- selected: store.getters.activeIssue.assignees,
+ selected: [],
participants,
};
},
@@ -82,6 +97,8 @@ describe('BoardCardAssigneeDropdown', () => {
return wrapper.findAll(GlDropdownItem).wrappers.find(node => node.text().indexOf(text) === 0);
};
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+
beforeEach(() => {
store.state.activeId = '1';
store.state.issues = {
@@ -91,10 +108,11 @@ describe('BoardCardAssigneeDropdown', () => {
},
};
- jest.spyOn(store, 'dispatch').mockResolvedValue();
+ dispatchSpy = jest.spyOn(store, 'dispatch').mockResolvedValue();
});
afterEach(() => {
+ window.gon = {};
jest.restoreAllMocks();
});
@@ -243,6 +261,30 @@ describe('BoardCardAssigneeDropdown', () => {
},
);
+ describe('when participants is loading', () => {
+ beforeEach(() => {
+ createComponent('', true);
+ });
+
+ it('finds a loading icon in the dropdown', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('when participants is loading is false', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('does not find GlLoading icon in the dropdown', () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+
+ it('finds at least 1 GlDropdownItem', () => {
+ expect(wrapper.findAll(GlDropdownItem).length).toBeGreaterThan(0);
+ });
+ });
+
describe('Apollo', () => {
beforeEach(() => {
getIssueParticipantsSpy = jest.fn().mockResolvedValue({
@@ -305,4 +347,39 @@ describe('BoardCardAssigneeDropdown', () => {
expect(wrapper.find(GlSearchBoxByType).exists()).toBe(true);
});
+
+ describe('when assign-self is emitted from IssuableAssignees', () => {
+ const currentUser = { username: 'self', name: '', id: '' };
+
+ beforeEach(() => {
+ window.gon = { current_username: currentUser.username };
+
+ dispatchSpy.mockResolvedValue([currentUser]);
+ createComponent();
+
+ wrapper.find(IssuableAssignees).vm.$emit('assign-self');
+ });
+
+ it('calls setAssignees with currentUser', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('setAssignees', currentUser.username);
+ });
+
+ it('adds the user to the selected list', async () => {
+ expect(findByText(currentUser.username).exists()).toBe(true);
+ });
+ });
+
+ describe('when setting an assignee', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('passes loading state from Vuex to BoardEditableItem', async () => {
+ store.state.isSettingAssignees = true;
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.find(BoardEditableItem).props('loading')).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/boards/components/board_column_new_spec.js b/spec/frontend/boards/components/board_column_new_spec.js
index 4aafc3a867a..81c0e60f931 100644
--- a/spec/frontend/boards/components/board_column_new_spec.js
+++ b/spec/frontend/boards/components/board_column_new_spec.js
@@ -2,7 +2,6 @@ import { shallowMount } from '@vue/test-utils';
import { listObj } from 'jest/boards/mock_data';
import BoardColumn from '~/boards/components/board_column_new.vue';
-import List from '~/boards/models/list';
import { ListType } from '~/boards/constants';
import { createStore } from '~/boards/stores';
@@ -20,24 +19,22 @@ describe('Board Column Component', () => {
const listMock = {
...listObj,
- list_type: listType,
+ listType,
collapsed,
};
if (listType === ListType.assignee) {
delete listMock.label;
- listMock.user = {};
+ listMock.assignee = {};
}
- const list = new List({ ...listMock, doNotFetchIssues: true });
-
store = createStore();
wrapper = shallowMount(BoardColumn, {
store,
propsData: {
disabled: false,
- list,
+ list: listMock,
},
provide: {
boardId,
@@ -60,7 +57,7 @@ describe('Board Column Component', () => {
it('has class is-collapsed when list is collapsed', () => {
createComponent({ collapsed: false });
- expect(wrapper.vm.list.isExpanded).toBe(true);
+ expect(isCollapsed()).toBe(false);
});
it('does not have class is-collapsed when list is expanded', () => {
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index 09e38001e2e..291013c561e 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -1,32 +1,38 @@
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlAlert } from '@gitlab/ui';
+import Draggable from 'vuedraggable';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
-import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
import getters from 'ee_else_ce/boards/stores/getters';
-import { mockListsWithModel } from '../mock_data';
+import BoardColumn from '~/boards/components/board_column.vue';
+import { mockLists, mockListsWithModel } from '../mock_data';
import BoardContent from '~/boards/components/board_content.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
+const actions = {
+ moveList: jest.fn(),
+};
+
describe('BoardContent', () => {
let wrapper;
const defaultState = {
isShowingEpicsSwimlanes: false,
- boardLists: mockListsWithModel,
+ boardLists: mockLists,
error: undefined,
};
const createStore = (state = defaultState) => {
return new Vuex.Store({
+ actions,
getters,
state,
});
};
- const createComponent = state => {
+ const createComponent = ({ state, props = {}, graphqlBoardListsEnabled = false } = {}) => {
const store = createStore({
...defaultState,
...state,
@@ -37,25 +43,61 @@ describe('BoardContent', () => {
lists: mockListsWithModel,
canAdminList: true,
disabled: false,
+ ...props,
+ },
+ provide: {
+ glFeatures: { graphqlBoardLists: graphqlBoardListsEnabled },
},
store,
});
};
- beforeEach(() => {
- createComponent();
- });
-
afterEach(() => {
wrapper.destroy();
});
it('renders a BoardColumn component per list', () => {
- expect(wrapper.findAll(BoardColumn)).toHaveLength(mockListsWithModel.length);
+ createComponent();
+
+ expect(wrapper.findAll(BoardColumn)).toHaveLength(mockLists.length);
});
it('does not display EpicsSwimlanes component', () => {
+ createComponent();
+
expect(wrapper.find(EpicsSwimlanes).exists()).toBe(false);
expect(wrapper.find(GlAlert).exists()).toBe(false);
});
+
+ describe('graphqlBoardLists feature flag enabled', () => {
+ describe('can admin list', () => {
+ beforeEach(() => {
+ createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: true } });
+ });
+
+ it('renders draggable component', () => {
+ expect(wrapper.find(Draggable).exists()).toBe(true);
+ });
+ });
+
+ describe('can not admin list', () => {
+ beforeEach(() => {
+ createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: false } });
+ });
+
+ it('renders draggable component', () => {
+ expect(wrapper.find(Draggable).exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('graphqlBoardLists feature flag disabled', () => {
+ beforeEach(() => {
+ createComponent({ graphqlBoardListsEnabled: false });
+ });
+
+ it('does not render draggable component', () => {
+ expect(wrapper.find(Draggable).exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index 65d8070192c..3b15cbb6b7e 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -1,47 +1,275 @@
-import { mount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'jest/helpers/test_constants';
+import { GlModal } from '@gitlab/ui';
+import waitForPromises from 'helpers/wait_for_promises';
+
+import axios from '~/lib/utils/axios_utils';
+import { visitUrl } from '~/lib/utils/url_utility';
import boardsStore from '~/boards/stores/boards_store';
-import boardForm from '~/boards/components/board_form.vue';
-import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import BoardForm from '~/boards/components/board_form.vue';
+import BoardConfigurationOptions from '~/boards/components/board_configuration_options.vue';
+import createBoardMutation from '~/boards/graphql/board.mutation.graphql';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn().mockName('visitUrlMock'),
+}));
+
+const currentBoard = {
+ id: 1,
+ name: 'test',
+ labels: [],
+ milestone_id: undefined,
+ assignee: {},
+ assignee_id: undefined,
+ weight: null,
+ hide_backlog_list: false,
+ hide_closed_list: false,
+};
+
+const boardDefaults = {
+ id: false,
+ name: '',
+ labels: [],
+ milestone_id: undefined,
+ assignee: {},
+ assignee_id: undefined,
+ weight: null,
+ hide_backlog_list: false,
+ hide_closed_list: false,
+};
+
+const defaultProps = {
+ canAdminBoard: false,
+ labelsPath: `${TEST_HOST}/labels/path`,
+ labelsWebUrl: `${TEST_HOST}/-/labels`,
+ currentBoard,
+};
-describe('board_form.vue', () => {
+const endpoints = {
+ boardsEndpoint: 'test-endpoint',
+};
+
+const mutate = jest.fn().mockResolvedValue({});
+
+describe('BoardForm', () => {
let wrapper;
+ let axiosMock;
- const propsData = {
- canAdminBoard: false,
- labelsPath: `${TEST_HOST}/labels/path`,
- labelsWebUrl: `${TEST_HOST}/-/labels`,
- };
+ const findModal = () => wrapper.find(GlModal);
+ const findModalActionPrimary = () => findModal().props('actionPrimary');
+ const findForm = () => wrapper.find('[data-testid="board-form"]');
+ const findFormWrapper = () => wrapper.find('[data-testid="board-form-wrapper"]');
+ const findDeleteConfirmation = () => wrapper.find('[data-testid="delete-confirmation-message"]');
+ const findConfigurationOptions = () => wrapper.find(BoardConfigurationOptions);
+ const findInput = () => wrapper.find('#board-new-name');
- const findModal = () => wrapper.find(DeprecatedModal);
+ const createComponent = (props, data) => {
+ wrapper = shallowMount(BoardForm, {
+ propsData: { ...defaultProps, ...props },
+ data() {
+ return {
+ ...data,
+ };
+ },
+ provide: {
+ endpoints,
+ },
+ mocks: {
+ $apollo: {
+ mutate,
+ },
+ },
+ attachToDocument: true,
+ });
+ };
beforeEach(() => {
- boardsStore.state.currentPage = 'edit';
- wrapper = mount(boardForm, { propsData });
+ axiosMock = new AxiosMockAdapter(axios);
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
+ axiosMock.restore();
+ boardsStore.state.currentPage = null;
});
- describe('methods', () => {
- describe('cancel', () => {
- it('resets currentPage', () => {
- wrapper.vm.cancel();
- expect(boardsStore.state.currentPage).toBe('');
+ describe('when user can not admin the board', () => {
+ beforeEach(() => {
+ boardsStore.state.currentPage = 'new';
+ createComponent();
+ });
+
+ it('hides modal footer when user is not a board admin', () => {
+ expect(findModal().attributes('hide-footer')).toBeDefined();
+ });
+
+ it('displays board scope title', () => {
+ expect(findModal().attributes('title')).toBe('Board scope');
+ });
+
+ it('does not display a form', () => {
+ expect(findForm().exists()).toBe(false);
+ });
+ });
+
+ describe('when user can admin the board', () => {
+ beforeEach(() => {
+ boardsStore.state.currentPage = 'new';
+ createComponent({ canAdminBoard: true });
+ });
+
+ it('shows modal footer when user is a board admin', () => {
+ expect(findModal().attributes('hide-footer')).toBeUndefined();
+ });
+
+ it('displays a form', () => {
+ expect(findForm().exists()).toBe(true);
+ });
+
+ it('focuses an input field', async () => {
+ expect(document.activeElement).toBe(wrapper.vm.$refs.name);
+ });
+ });
+
+ describe('when creating a new board', () => {
+ beforeEach(() => {
+ boardsStore.state.currentPage = 'new';
+ });
+
+ describe('on non-scoped-board', () => {
+ beforeEach(() => {
+ createComponent({ canAdminBoard: true });
+ });
+
+ it('clears the form', () => {
+ expect(findConfigurationOptions().props('board')).toEqual(boardDefaults);
+ });
+
+ it('shows a correct title about creating a board', () => {
+ expect(findModal().attributes('title')).toBe('Create new board');
+ });
+
+ it('passes correct primary action text and variant', () => {
+ expect(findModalActionPrimary().text).toBe('Create board');
+ expect(findModalActionPrimary().attributes[0].variant).toBe('success');
+ });
+
+ it('does not render delete confirmation message', () => {
+ expect(findDeleteConfirmation().exists()).toBe(false);
+ });
+
+ it('renders form wrapper', () => {
+ expect(findFormWrapper().exists()).toBe(true);
+ });
+
+ it('passes a true isNewForm prop to BoardConfigurationOptions component', () => {
+ expect(findConfigurationOptions().props('isNewForm')).toBe(true);
+ });
+ });
+
+ describe('when submitting a create event', () => {
+ beforeEach(() => {
+ const url = `${endpoints.boardsEndpoint}.json`;
+ axiosMock.onPost(url).reply(200, { id: '2', board_path: 'new path' });
+ });
+
+ it('does not call API if board name is empty', async () => {
+ createComponent({ canAdminBoard: true });
+ findInput().trigger('keyup.enter', { metaKey: true });
+
+ await waitForPromises();
+
+ expect(mutate).not.toHaveBeenCalled();
+ });
+
+ it('calls REST and GraphQL API and redirects to correct page', async () => {
+ createComponent({ canAdminBoard: true });
+
+ findInput().value = 'Test name';
+ findInput().trigger('input');
+ findInput().trigger('keyup.enter', { metaKey: true });
+
+ await waitForPromises();
+
+ expect(axiosMock.history.post[0].data).toBe(
+ JSON.stringify({ board: { ...boardDefaults, name: 'test', label_ids: [''] } }),
+ );
+
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: createBoardMutation,
+ variables: {
+ id: 'gid://gitlab/Board/2',
+ },
+ });
+
+ await waitForPromises();
+ expect(visitUrl).toHaveBeenCalledWith('new path');
});
});
});
- describe('buttons', () => {
- it('cancel button triggers cancel()', () => {
- wrapper.setMethods({ cancel: jest.fn() });
- findModal().vm.$emit('cancel');
+ describe('when editing a board', () => {
+ beforeEach(() => {
+ boardsStore.state.currentPage = 'edit';
+ });
+
+ describe('on non-scoped-board', () => {
+ beforeEach(() => {
+ createComponent({ canAdminBoard: true });
+ });
+
+ it('clears the form', () => {
+ expect(findConfigurationOptions().props('board')).toEqual(currentBoard);
+ });
+
+ it('shows a correct title about creating a board', () => {
+ expect(findModal().attributes('title')).toBe('Edit board');
+ });
+
+ it('passes correct primary action text and variant', () => {
+ expect(findModalActionPrimary().text).toBe('Save changes');
+ expect(findModalActionPrimary().attributes[0].variant).toBe('info');
+ });
+
+ it('does not render delete confirmation message', () => {
+ expect(findDeleteConfirmation().exists()).toBe(false);
+ });
+
+ it('renders form wrapper', () => {
+ expect(findFormWrapper().exists()).toBe(true);
+ });
+
+ it('passes a false isNewForm prop to BoardConfigurationOptions component', () => {
+ expect(findConfigurationOptions().props('isNewForm')).toBe(false);
+ });
+ });
+
+ describe('when submitting an update event', () => {
+ beforeEach(() => {
+ const url = endpoints.boardsEndpoint;
+ axiosMock.onPut(url).reply(200, { board_path: 'new path' });
+ });
+
+ it('calls REST and GraphQL API with correct parameters', async () => {
+ createComponent({ canAdminBoard: true });
+
+ findInput().trigger('keyup.enter', { metaKey: true });
+
+ await waitForPromises();
+
+ expect(axiosMock.history.put[0].data).toBe(
+ JSON.stringify({ board: { ...currentBoard, label_ids: [''] } }),
+ );
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.cancel).toHaveBeenCalled();
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: createBoardMutation,
+ variables: {
+ id: `gid://gitlab/Board/${currentBoard.id}`,
+ },
+ });
});
});
});
diff --git a/spec/frontend/boards/components/board_list_header_new_spec.js b/spec/frontend/boards/components/board_list_header_new_spec.js
index 80786d82620..7428dfae83f 100644
--- a/spec/frontend/boards/components/board_list_header_new_spec.js
+++ b/spec/frontend/boards/components/board_list_header_new_spec.js
@@ -1,9 +1,8 @@
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { listObj } from 'jest/boards/mock_data';
+import { mockLabelList } from 'jest/boards/mock_data';
import BoardListHeader from '~/boards/components/board_list_header_new.vue';
-import List from '~/boards/models/list';
import { ListType } from '~/boards/constants';
const localVue = createLocalVue();
@@ -32,21 +31,19 @@ describe('Board List Header Component', () => {
const boardId = '1';
const listMock = {
- ...listObj,
- list_type: listType,
+ ...mockLabelList,
+ listType,
collapsed,
};
if (listType === ListType.assignee) {
delete listMock.label;
- listMock.user = {};
+ listMock.assignee = {};
}
- const list = new List({ ...listMock, doNotFetchIssues: true });
-
if (withLocalStorage) {
localStorage.setItem(
- `boards.${boardId}.${list.type}.${list.id}.expanded`,
+ `boards.${boardId}.${listMock.listType}.${listMock.id}.expanded`,
(!collapsed).toString(),
);
}
@@ -62,7 +59,7 @@ describe('Board List Header Component', () => {
localVue,
propsData: {
disabled: false,
- list,
+ list: listMock,
},
provide: {
boardId,
@@ -72,14 +69,15 @@ describe('Board List Header Component', () => {
});
};
- const isExpanded = () => wrapper.vm.list.isExpanded;
- const isCollapsed = () => !isExpanded();
+ const isCollapsed = () => wrapper.vm.list.collapsed;
+ const isExpanded = () => !isCollapsed;
const findAddIssueButton = () => wrapper.find({ ref: 'newIssueBtn' });
+ const findTitle = () => wrapper.find('.board-title');
const findCaret = () => wrapper.find('.board-title-caret');
describe('Add issue button', () => {
- const hasNoAddButton = [ListType.promotion, ListType.blank, ListType.closed];
+ const hasNoAddButton = [ListType.closed];
const hasAddButton = [ListType.backlog, ListType.label, ListType.milestone, ListType.assignee];
it.each(hasNoAddButton)('does not render when List Type is `%s`', listType => {
@@ -125,7 +123,7 @@ describe('Board List Header Component', () => {
it('collapses expanded Column when clicking the collapse icon', async () => {
createComponent();
- expect(isExpanded()).toBe(true);
+ expect(isCollapsed()).toBe(false);
findCaret().vm.$emit('click');
@@ -166,4 +164,24 @@ describe('Board List Header Component', () => {
expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(String(isExpanded()));
});
});
+
+ describe('user can drag', () => {
+ const cannotDragList = [ListType.backlog, ListType.closed];
+ const canDragList = [ListType.label, ListType.milestone, ListType.assignee];
+
+ it.each(cannotDragList)(
+ 'does not have user-can-drag-class so user cannot drag list',
+ listType => {
+ createComponent({ listType });
+
+ expect(findTitle().classes()).not.toContain('user-can-drag');
+ },
+ );
+
+ it.each(canDragList)('has user-can-drag-class so user can drag list', listType => {
+ createComponent({ listType });
+
+ expect(findTitle().classes()).toContain('user-can-drag');
+ });
+ });
});
diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js
index 2439c347bf0..656a503bb86 100644
--- a/spec/frontend/boards/components/board_list_header_spec.js
+++ b/spec/frontend/boards/components/board_list_header_spec.js
@@ -73,7 +73,7 @@ describe('Board List Header Component', () => {
const findCaret = () => wrapper.find('.board-title-caret');
describe('Add issue button', () => {
- const hasNoAddButton = [ListType.promotion, ListType.blank, ListType.closed];
+ const hasNoAddButton = [ListType.closed];
const hasAddButton = [ListType.backlog, ListType.label, ListType.milestone, ListType.assignee];
it.each(hasNoAddButton)('does not render when List Type is `%s`', listType => {
diff --git a/spec/frontend/boards/components/board_new_issue_new_spec.js b/spec/frontend/boards/components/board_new_issue_new_spec.js
index af4bad65121..ee1c4f31cf0 100644
--- a/spec/frontend/boards/components/board_new_issue_new_spec.js
+++ b/spec/frontend/boards/components/board_new_issue_new_spec.js
@@ -3,7 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import BoardNewIssue from '~/boards/components/board_new_issue_new.vue';
import '~/boards/models/list';
-import { mockListsWithModel } from '../mock_data';
+import { mockList } from '../mock_data';
const localVue = createLocalVue();
@@ -37,7 +37,7 @@ describe('Issue boards new issue form', () => {
wrapper = shallowMount(BoardNewIssue, {
propsData: {
disabled: false,
- list: mockListsWithModel[0],
+ list: mockList,
},
store,
localVue,
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js
index 2b7605a3f7c..db3c8c22950 100644
--- a/spec/frontend/boards/components/boards_selector_spec.js
+++ b/spec/frontend/boards/components/boards_selector_spec.js
@@ -1,6 +1,6 @@
import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
-import { GlDeprecatedDropdown, GlLoadingIcon } from '@gitlab/ui';
+import { GlDropdown, GlLoadingIcon, GlDropdownSectionHeader } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants';
import BoardsSelector from '~/boards/components/boards_selector.vue';
import boardsStore from '~/boards/stores/boards_store';
@@ -34,8 +34,9 @@ describe('BoardsSelector', () => {
};
const getDropdownItems = () => wrapper.findAll('.js-dropdown-item');
- const getDropdownHeaders = () => wrapper.findAll('.dropdown-bold-header');
+ const getDropdownHeaders = () => wrapper.findAll(GlDropdownSectionHeader);
const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findDropdown = () => wrapper.find(GlDropdown);
beforeEach(() => {
const $apollo = {
@@ -103,7 +104,7 @@ describe('BoardsSelector', () => {
});
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
- wrapper.find(GlDeprecatedDropdown).vm.$emit('show');
+ findDropdown().vm.$emit('show');
});
afterEach(() => {
@@ -125,7 +126,10 @@ describe('BoardsSelector', () => {
});
describe('loaded', () => {
- beforeEach(() => {
+ beforeEach(async () => {
+ await wrapper.setData({
+ loadingBoards: false,
+ });
return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick());
});
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
new file mode 100644
index 00000000000..74d88d9f34c
--- /dev/null
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
@@ -0,0 +1,152 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { mockMilestone as TEST_MILESTONE } from 'jest/boards/mock_data';
+import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
+import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
+import { createStore } from '~/boards/stores';
+import createFlash from '~/flash';
+
+const TEST_ISSUE = { id: 'gid://gitlab/Issue/1', iid: 9, referencePath: 'h/b#2' };
+
+jest.mock('~/flash');
+
+describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () => {
+ let wrapper;
+ let store;
+
+ afterEach(() => {
+ wrapper.destroy();
+ store = null;
+ wrapper = null;
+ });
+
+ const createWrapper = ({ milestone = null } = {}) => {
+ store = createStore();
+ store.state.issues = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } };
+ store.state.activeId = TEST_ISSUE.id;
+
+ wrapper = shallowMount(BoardSidebarMilestoneSelect, {
+ store,
+ provide: {
+ canUpdate: true,
+ },
+ data: () => ({
+ milestones: [TEST_MILESTONE],
+ }),
+ stubs: {
+ 'board-editable-item': BoardEditableItem,
+ },
+ mocks: {
+ $apollo: {
+ loading: false,
+ },
+ },
+ });
+ };
+
+ const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
+ const findLoader = () => wrapper.find(GlLoadingIcon);
+ const findDropdownItem = () => wrapper.find('[data-testid="milestone-item"]');
+ const findUnsetMilestoneItem = () => wrapper.find('[data-testid="no-milestone-item"]');
+ const findNoMilestonesFoundItem = () => wrapper.find('[data-testid="no-milestones-found"]');
+
+ it('renders "None" when no milestone is selected', () => {
+ createWrapper();
+
+ expect(findCollapsed().text()).toBe('None');
+ });
+
+ it('renders milestone title when set', () => {
+ createWrapper({ milestone: TEST_MILESTONE });
+
+ expect(findCollapsed().text()).toContain(TEST_MILESTONE.title);
+ });
+
+ it('shows loader while Apollo is loading', async () => {
+ createWrapper({ milestone: TEST_MILESTONE });
+
+ expect(findLoader().exists()).toBe(false);
+
+ wrapper.vm.$apollo.loading = true;
+ await wrapper.vm.$nextTick();
+
+ expect(findLoader().exists()).toBe(true);
+ });
+
+ it('shows message when error or no milestones found', async () => {
+ createWrapper();
+
+ wrapper.setData({ milestones: [] });
+ await wrapper.vm.$nextTick();
+
+ expect(findNoMilestonesFoundItem().text()).toBe('No milestones found');
+ });
+
+ describe('when milestone is selected', () => {
+ beforeEach(async () => {
+ createWrapper();
+
+ jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
+ store.state.issues[TEST_ISSUE.id].milestone = TEST_MILESTONE;
+ });
+ findDropdownItem().vm.$emit('click');
+ await wrapper.vm.$nextTick();
+ });
+
+ it('collapses sidebar and renders selected milestone', () => {
+ expect(findCollapsed().isVisible()).toBe(true);
+ expect(findCollapsed().text()).toContain(TEST_MILESTONE.title);
+ });
+
+ it('commits change to the server', () => {
+ expect(wrapper.vm.setActiveIssueMilestone).toHaveBeenCalledWith({
+ milestoneId: TEST_MILESTONE.id,
+ projectPath: 'h/b',
+ });
+ });
+ });
+
+ describe('when milestone is set to "None"', () => {
+ beforeEach(async () => {
+ createWrapper({ milestone: TEST_MILESTONE });
+
+ jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
+ store.state.issues[TEST_ISSUE.id].milestone = null;
+ });
+ findUnsetMilestoneItem().vm.$emit('click');
+ await wrapper.vm.$nextTick();
+ });
+
+ it('collapses sidebar and renders "None"', () => {
+ expect(findCollapsed().isVisible()).toBe(true);
+ expect(findCollapsed().text()).toBe('None');
+ });
+
+ it('commits change to the server', () => {
+ expect(wrapper.vm.setActiveIssueMilestone).toHaveBeenCalledWith({
+ milestoneId: null,
+ projectPath: 'h/b',
+ });
+ });
+ });
+
+ describe('when the mutation fails', () => {
+ const testMilestone = { id: '1', title: 'Former milestone' };
+
+ beforeEach(async () => {
+ createWrapper({ milestone: testMilestone });
+
+ jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
+ throw new Error(['failed mutation']);
+ });
+ findDropdownItem().vm.$emit('click');
+ await wrapper.vm.$nextTick();
+ });
+
+ it('collapses sidebar and renders former milestone', () => {
+ expect(findCollapsed().isVisible()).toBe(true);
+ expect(findCollapsed().text()).toContain(testMilestone.title);
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/boards/list_spec.js b/spec/frontend/boards/list_spec.js
index 9c3a6e66ef4..b731bb6e474 100644
--- a/spec/frontend/boards/list_spec.js
+++ b/spec/frontend/boards/list_spec.js
@@ -184,7 +184,6 @@ describe('List model', () => {
}),
);
list.issues = [];
- global.gon.features = { boardsWithSwimlanes: false };
});
it('adds new issue to top of list', done => {
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index 58f67231d55..ea6c52c6830 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -1,10 +1,8 @@
-/* global ListIssue */
/* global List */
import Vue from 'vue';
import { keyBy } from 'lodash';
import '~/boards/models/list';
-import '~/boards/models/issue';
import boardsStore from '~/boards/stores/boards_store';
export const boardObj = {
@@ -99,7 +97,7 @@ export const mockMilestone = {
due_date: '2019-12-31',
};
-const assignees = [
+export const assignees = [
{
id: 'gid://gitlab/User/2',
username: 'angelina.herman',
@@ -184,8 +182,6 @@ export const mockActiveIssue = {
emailsDisabled: false,
};
-export const mockIssueWithModel = new ListIssue(mockIssue);
-
export const mockIssue2 = {
id: 'gid://gitlab/Issue/437',
iid: 28,
@@ -203,8 +199,6 @@ export const mockIssue2 = {
},
};
-export const mockIssue2WithModel = new ListIssue(mockIssue2);
-
export const mockIssue3 = {
id: 'gid://gitlab/Issue/438',
iid: 29,
@@ -288,38 +282,39 @@ export const setMockEndpoints = (opts = {}) => {
});
};
-export const mockLists = [
- {
- id: 'gid://gitlab/List/1',
- title: 'Backlog',
- position: null,
- listType: 'backlog',
- collapsed: false,
- label: null,
- assignee: null,
- milestone: null,
- loading: false,
- issuesSize: 1,
- },
- {
- id: 'gid://gitlab/List/2',
+export const mockList = {
+ id: 'gid://gitlab/List/1',
+ title: 'Backlog',
+ position: null,
+ listType: 'backlog',
+ collapsed: false,
+ label: null,
+ assignee: null,
+ milestone: null,
+ loading: false,
+ issuesCount: 1,
+};
+
+export const mockLabelList = {
+ id: 'gid://gitlab/List/2',
+ title: 'To Do',
+ position: 0,
+ listType: 'label',
+ collapsed: false,
+ label: {
+ id: 'gid://gitlab/GroupLabel/121',
title: 'To Do',
- position: 0,
- listType: 'label',
- collapsed: false,
- label: {
- id: 'gid://gitlab/GroupLabel/121',
- title: 'To Do',
- color: '#F0AD4E',
- textColor: '#FFFFFF',
- description: null,
- },
- assignee: null,
- milestone: null,
- loading: false,
- issuesSize: 0,
+ color: '#F0AD4E',
+ textColor: '#FFFFFF',
+ description: null,
},
-];
+ assignee: null,
+ milestone: null,
+ loading: false,
+ issuesCount: 0,
+};
+
+export const mockLists = [mockList, mockLabelList];
export const mockListsById = keyBy(mockLists, 'id');
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index 4d529580a7a..0cae6456887 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -1,23 +1,25 @@
import testAction from 'helpers/vuex_action_helper';
import {
- mockListsWithModel,
mockLists,
mockListsById,
mockIssue,
- mockIssueWithModel,
- mockIssue2WithModel,
+ mockIssue2,
rawIssue,
mockIssues,
+ mockMilestone,
labels,
mockActiveIssue,
} from '../mock_data';
import actions, { gqlClient } from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types';
import { inactiveId } from '~/boards/constants';
-import issueMoveListMutation from '~/boards/queries/issue_move_list.mutation.graphql';
-import destroyBoardListMutation from '~/boards/queries/board_list_destroy.mutation.graphql';
+import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.graphql';
+import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql';
import updateAssignees from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql';
import { fullBoardId, formatListIssues, formatBoardLists } from '~/boards/boards_util';
+import createFlash from '~/flash';
+
+jest.mock('~/flash');
const expectNotImplemented = action => {
it('is not implemented', () => {
@@ -29,6 +31,10 @@ const expectNotImplemented = action => {
// subgroups when the movIssue action is called.
const getProjectPath = path => path.split('#')[0];
+beforeEach(() => {
+ window.gon = { features: {} };
+});
+
describe('setInitialBoardData', () => {
it('sets data object', () => {
const mockData = {
@@ -65,6 +71,24 @@ describe('setFilters', () => {
});
});
+describe('performSearch', () => {
+ it('should dispatch setFilters action', done => {
+ testAction(actions.performSearch, {}, {}, [], [{ type: 'setFilters', payload: {} }], done);
+ });
+
+ it('should dispatch setFilters, fetchLists and resetIssues action when graphqlBoardLists FF is on', done => {
+ window.gon = { features: { graphqlBoardLists: true } };
+ testAction(
+ actions.performSearch,
+ {},
+ {},
+ [],
+ [{ type: 'setFilters', payload: {} }, { type: 'fetchLists' }, { type: 'resetIssues' }],
+ done,
+ );
+ });
+});
+
describe('setActiveId', () => {
it('should commit mutation SET_ACTIVE_ID', done => {
const state = {
@@ -120,7 +144,7 @@ describe('fetchLists', () => {
payload: formattedLists,
},
],
- [{ type: 'generateDefaultLists' }],
+ [],
done,
);
});
@@ -150,37 +174,12 @@ describe('fetchLists', () => {
payload: formattedLists,
},
],
- [{ type: 'createList', payload: { backlog: true } }, { type: 'generateDefaultLists' }],
+ [{ type: 'createList', payload: { backlog: true } }],
done,
);
});
});
-describe('generateDefaultLists', () => {
- let store;
- beforeEach(() => {
- const state = {
- endpoints: { fullPath: 'gitlab-org', boardId: '1' },
- boardType: 'group',
- disabled: false,
- boardLists: [{ type: 'backlog' }, { type: 'closed' }],
- };
-
- store = {
- commit: jest.fn(),
- dispatch: jest.fn(() => Promise.resolve()),
- state,
- };
- });
-
- it('should dispatch fetchLabels', () => {
- return actions.generateDefaultLists(store).then(() => {
- expect(store.dispatch.mock.calls[0]).toEqual(['fetchLabels', 'to do']);
- expect(store.dispatch.mock.calls[1]).toEqual(['fetchLabels', 'doing']);
- });
- });
-});
-
describe('createList', () => {
it('should dispatch addList action when creating backlog list', done => {
const backlogList = {
@@ -251,8 +250,8 @@ describe('createList', () => {
describe('moveList', () => {
it('should commit MOVE_LIST mutation and dispatch updateList action', done => {
const initialBoardListsState = {
- 'gid://gitlab/List/1': mockListsWithModel[0],
- 'gid://gitlab/List/2': mockListsWithModel[1],
+ 'gid://gitlab/List/1': mockLists[0],
+ 'gid://gitlab/List/2': mockLists[1],
};
const state = {
@@ -274,7 +273,7 @@ describe('moveList', () => {
[
{
type: types.MOVE_LIST,
- payload: { movedList: mockListsWithModel[0], listAtNewIndex: mockListsWithModel[1] },
+ payload: { movedList: mockLists[0], listAtNewIndex: mockLists[1] },
},
],
[
@@ -290,6 +289,33 @@ describe('moveList', () => {
done,
);
});
+
+ it('should not commit MOVE_LIST or dispatch updateList if listId and replacedListId are the same', () => {
+ const initialBoardListsState = {
+ 'gid://gitlab/List/1': mockLists[0],
+ 'gid://gitlab/List/2': mockLists[1],
+ };
+
+ const state = {
+ endpoints: { fullPath: 'gitlab-org', boardId: '1' },
+ boardType: 'group',
+ disabled: false,
+ boardLists: initialBoardListsState,
+ };
+
+ testAction(
+ actions.moveList,
+ {
+ listId: 'gid://gitlab/List/1',
+ replacedListId: 'gid://gitlab/List/1',
+ newIndex: 1,
+ adjustmentValue: 1,
+ },
+ state,
+ [],
+ [],
+ );
+ });
});
describe('updateList', () => {
@@ -499,15 +525,15 @@ describe('moveIssue', () => {
};
const issues = {
- '436': mockIssueWithModel,
- '437': mockIssue2WithModel,
+ '436': mockIssue,
+ '437': mockIssue2,
};
const state = {
endpoints: { fullPath: 'gitlab-org', boardId: '1' },
boardType: 'group',
disabled: false,
- boardLists: mockListsWithModel,
+ boardLists: mockLists,
issuesByListId: listIssues,
issues,
};
@@ -536,7 +562,7 @@ describe('moveIssue', () => {
{
type: types.MOVE_ISSUE,
payload: {
- originalIssue: mockIssueWithModel,
+ originalIssue: mockIssue,
fromListId: 'gid://gitlab/List/1',
toListId: 'gid://gitlab/List/2',
},
@@ -611,7 +637,7 @@ describe('moveIssue', () => {
{
type: types.MOVE_ISSUE,
payload: {
- originalIssue: mockIssueWithModel,
+ originalIssue: mockIssue,
fromListId: 'gid://gitlab/List/1',
toListId: 'gid://gitlab/List/2',
},
@@ -619,7 +645,7 @@ describe('moveIssue', () => {
{
type: types.MOVE_ISSUE_FAILURE,
payload: {
- originalIssue: mockIssueWithModel,
+ originalIssue: mockIssue,
fromListId: 'gid://gitlab/List/1',
toListId: 'gid://gitlab/List/2',
originalIndex: 0,
@@ -639,38 +665,59 @@ describe('setAssignees', () => {
const refPath = `${projectPath}#3`;
const iid = '1';
- beforeEach(() => {
- jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
- data: { issueSetAssignees: { issue: { assignees: { nodes: [{ ...node }] } } } },
+ describe('when succeeds', () => {
+ beforeEach(() => {
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: { issueSetAssignees: { issue: { assignees: { nodes: [{ ...node }] } } } },
+ });
});
- });
- it('calls mutate with the correct values', async () => {
- await actions.setAssignees(
- { commit: () => {}, getters: { activeIssue: { iid, referencePath: refPath } } },
- [name],
- );
+ it('calls mutate with the correct values', async () => {
+ await actions.setAssignees(
+ { commit: () => {}, getters: { activeIssue: { iid, referencePath: refPath } } },
+ [name],
+ );
+
+ expect(gqlClient.mutate).toHaveBeenCalledWith({
+ mutation: updateAssignees,
+ variables: { iid, assigneeUsernames: [name], projectPath },
+ });
+ });
- expect(gqlClient.mutate).toHaveBeenCalledWith({
- mutation: updateAssignees,
- variables: { iid, assigneeUsernames: [name], projectPath },
+ it('calls the correct mutation with the correct values', done => {
+ testAction(
+ actions.setAssignees,
+ {},
+ { activeIssue: { iid, referencePath: refPath }, commit: () => {} },
+ [
+ { type: types.SET_ASSIGNEE_LOADING, payload: true },
+ {
+ type: 'UPDATE_ISSUE_BY_ID',
+ payload: { prop: 'assignees', issueId: undefined, value: [node] },
+ },
+ { type: types.SET_ASSIGNEE_LOADING, payload: false },
+ ],
+ [],
+ done,
+ );
});
});
- it('calls the correct mutation with the correct values', done => {
- testAction(
- actions.setAssignees,
- {},
- { activeIssue: { iid, referencePath: refPath }, commit: () => {} },
- [
- {
- type: 'UPDATE_ISSUE_BY_ID',
- payload: { prop: 'assignees', issueId: undefined, value: [node] },
- },
- ],
- [],
- done,
- );
+ describe('when fails', () => {
+ beforeEach(() => {
+ jest.spyOn(gqlClient, 'mutate').mockRejectedValue();
+ });
+
+ it('calls createFlash', async () => {
+ await actions.setAssignees({
+ commit: () => {},
+ getters: { activeIssue: { iid, referencePath: refPath } },
+ });
+
+ expect(createFlash).toHaveBeenCalledWith({
+ message: 'An error occurred while updating assignees.',
+ });
+ });
});
});
@@ -885,6 +932,60 @@ describe('setActiveIssueSubscribed', () => {
});
});
+describe('setActiveIssueMilestone', () => {
+ const state = { issues: { [mockIssue.id]: mockIssue } };
+ const getters = { activeIssue: mockIssue };
+ const testMilestone = {
+ ...mockMilestone,
+ id: 'gid://gitlab/Milestone/1',
+ };
+ const input = {
+ milestoneId: testMilestone.id,
+ projectPath: 'h/b',
+ };
+
+ it('should commit milestone after setting the issue', done => {
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ updateIssue: {
+ issue: {
+ milestone: testMilestone,
+ },
+ errors: [],
+ },
+ },
+ });
+
+ const payload = {
+ issueId: getters.activeIssue.id,
+ prop: 'milestone',
+ value: testMilestone,
+ };
+
+ testAction(
+ actions.setActiveIssueMilestone,
+ input,
+ { ...state, ...getters },
+ [
+ {
+ type: types.UPDATE_ISSUE_BY_ID,
+ payload,
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('throws error if fails', async () => {
+ jest
+ .spyOn(gqlClient, 'mutate')
+ .mockResolvedValue({ data: { updateIssue: { errors: ['failed mutation'] } } });
+
+ await expect(actions.setActiveIssueMilestone({ getters }, input)).rejects.toThrow(Error);
+ });
+});
+
describe('fetchBacklog', () => {
expectNotImplemented(actions.fetchBacklog);
});
diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js
index 64025726dd1..6ceb8867d1f 100644
--- a/spec/frontend/boards/stores/getters_spec.js
+++ b/spec/frontend/boards/stores/getters_spec.js
@@ -6,28 +6,10 @@ import {
mockIssues,
mockIssuesByListId,
issues,
- mockListsWithModel,
+ mockLists,
} from '../mock_data';
describe('Boards - Getters', () => {
- describe('labelToggleState', () => {
- it('should return "on" when isShowingLabels is true', () => {
- const state = {
- isShowingLabels: true,
- };
-
- expect(getters.labelToggleState(state)).toBe('on');
- });
-
- it('should return "off" when isShowingLabels is false', () => {
- const state = {
- isShowingLabels: false,
- };
-
- expect(getters.labelToggleState(state)).toBe('off');
- });
- });
-
describe('isSidebarOpen', () => {
it('returns true when activeId is not equal to 0', () => {
const state = {
@@ -51,52 +33,8 @@ describe('Boards - Getters', () => {
window.gon = { features: {} };
});
- describe('when boardsWithSwimlanes is true', () => {
- beforeEach(() => {
- window.gon = { features: { boardsWithSwimlanes: true } };
- });
-
- describe('when isShowingEpicsSwimlanes is true', () => {
- it('returns true', () => {
- const state = {
- isShowingEpicsSwimlanes: true,
- };
-
- expect(getters.isSwimlanesOn(state)).toBe(true);
- });
- });
-
- describe('when isShowingEpicsSwimlanes is false', () => {
- it('returns false', () => {
- const state = {
- isShowingEpicsSwimlanes: false,
- };
-
- expect(getters.isSwimlanesOn(state)).toBe(false);
- });
- });
- });
-
- describe('when boardsWithSwimlanes is false', () => {
- describe('when isShowingEpicsSwimlanes is true', () => {
- it('returns false', () => {
- const state = {
- isShowingEpicsSwimlanes: true,
- };
-
- expect(getters.isSwimlanesOn(state)).toBe(false);
- });
- });
-
- describe('when isShowingEpicsSwimlanes is false', () => {
- it('returns false', () => {
- const state = {
- isShowingEpicsSwimlanes: false,
- };
-
- expect(getters.isSwimlanesOn(state)).toBe(false);
- });
- });
+ it('returns false', () => {
+ expect(getters.isSwimlanesOn()).toBe(false);
});
});
@@ -156,22 +94,22 @@ describe('Boards - Getters', () => {
const boardsState = {
boardLists: {
- 'gid://gitlab/List/1': mockListsWithModel[0],
- 'gid://gitlab/List/2': mockListsWithModel[1],
+ 'gid://gitlab/List/1': mockLists[0],
+ 'gid://gitlab/List/2': mockLists[1],
},
};
describe('getListByLabelId', () => {
it('returns list for a given label id', () => {
expect(getters.getListByLabelId(boardsState)('gid://gitlab/GroupLabel/121')).toEqual(
- mockListsWithModel[1],
+ mockLists[1],
);
});
});
describe('getListByTitle', () => {
it('returns list for a given list title', () => {
- expect(getters.getListByTitle(boardsState)('To Do')).toEqual(mockListsWithModel[1]);
+ expect(getters.getListByTitle(boardsState)('To Do')).toEqual(mockLists[1]);
});
});
});
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index e1e57a8fd43..d93119ede3d 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -1,15 +1,7 @@
import mutations from '~/boards/stores/mutations';
import * as types from '~/boards/stores/mutation_types';
import defaultState from '~/boards/stores/state';
-import {
- mockListsWithModel,
- mockLists,
- rawIssue,
- mockIssue,
- mockIssue2,
- mockIssueWithModel,
- mockIssue2WithModel,
-} from '../mock_data';
+import { mockLists, rawIssue, mockIssue, mockIssue2 } from '../mock_data';
const expectNotImplemented = action => {
it('is not implemented', () => {
@@ -21,8 +13,8 @@ describe('Board Store Mutations', () => {
let state;
const initialBoardListsState = {
- 'gid://gitlab/List/1': mockListsWithModel[0],
- 'gid://gitlab/List/2': mockListsWithModel[1],
+ 'gid://gitlab/List/1': mockLists[0],
+ 'gid://gitlab/List/2': mockLists[1],
};
beforeEach(() => {
@@ -41,19 +33,21 @@ describe('Board Store Mutations', () => {
};
const boardType = 'group';
const disabled = false;
- const showPromotion = false;
+ const boardConfig = {
+ milestoneTitle: 'Milestone 1',
+ };
mutations[types.SET_INITIAL_BOARD_DATA](state, {
...endpoints,
boardType,
disabled,
- showPromotion,
+ boardConfig,
});
expect(state.endpoints).toEqual(endpoints);
expect(state.boardType).toEqual(boardType);
expect(state.disabled).toEqual(disabled);
- expect(state.showPromotion).toEqual(showPromotion);
+ expect(state.boardConfig).toEqual(boardConfig);
});
});
@@ -135,10 +129,10 @@ describe('Board Store Mutations', () => {
describe('RECEIVE_ADD_LIST_SUCCESS', () => {
it('adds list to boardLists state', () => {
- mutations.RECEIVE_ADD_LIST_SUCCESS(state, mockListsWithModel[0]);
+ mutations.RECEIVE_ADD_LIST_SUCCESS(state, mockLists[0]);
expect(state.boardLists).toEqual({
- [mockListsWithModel[0].id]: mockListsWithModel[0],
+ [mockLists[0].id]: mockLists[0],
});
});
});
@@ -155,13 +149,13 @@ describe('Board Store Mutations', () => {
};
mutations.MOVE_LIST(state, {
- movedList: mockListsWithModel[0],
- listAtNewIndex: mockListsWithModel[1],
+ movedList: mockLists[0],
+ listAtNewIndex: mockLists[1],
});
expect(state.boardLists).toEqual({
- 'gid://gitlab/List/2': mockListsWithModel[1],
- 'gid://gitlab/List/1': mockListsWithModel[0],
+ 'gid://gitlab/List/2': mockLists[1],
+ 'gid://gitlab/List/1': mockLists[0],
});
});
});
@@ -171,8 +165,8 @@ describe('Board Store Mutations', () => {
state = {
...state,
boardLists: {
- 'gid://gitlab/List/2': mockListsWithModel[1],
- 'gid://gitlab/List/1': mockListsWithModel[0],
+ 'gid://gitlab/List/2': mockLists[1],
+ 'gid://gitlab/List/1': mockLists[0],
},
error: undefined,
};
@@ -186,7 +180,7 @@ describe('Board Store Mutations', () => {
describe('REMOVE_LIST', () => {
it('removes list from boardLists', () => {
- const [list, secondList] = mockListsWithModel;
+ const [list, secondList] = mockLists;
const expected = {
[secondList.id]: secondList,
};
@@ -355,8 +349,8 @@ describe('Board Store Mutations', () => {
};
const issues = {
- '1': mockIssueWithModel,
- '2': mockIssue2WithModel,
+ '1': mockIssue,
+ '2': mockIssue2,
};
state = {
@@ -367,7 +361,7 @@ describe('Board Store Mutations', () => {
};
mutations.MOVE_ISSUE(state, {
- originalIssue: mockIssue2WithModel,
+ originalIssue: mockIssue2,
fromListId: 'gid://gitlab/List/1',
toListId: 'gid://gitlab/List/2',
});
@@ -396,7 +390,7 @@ describe('Board Store Mutations', () => {
issue: rawIssue,
});
- expect(state.issues).toEqual({ '436': { ...mockIssueWithModel, id: 436 } });
+ expect(state.issues).toEqual({ '436': { ...mockIssue, id: 436 } });
});
});
@@ -466,13 +460,13 @@ describe('Board Store Mutations', () => {
boardLists: initialBoardListsState,
};
- expect(state.boardLists['gid://gitlab/List/1'].issuesSize).toBe(1);
+ expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1);
- mutations.ADD_ISSUE_TO_LIST(state, { list: mockListsWithModel[0], issue: mockIssue2 });
+ mutations.ADD_ISSUE_TO_LIST(state, { list: mockLists[0], issue: mockIssue2 });
expect(state.issuesByListId['gid://gitlab/List/1']).toContain(mockIssue2.id);
expect(state.issues[mockIssue2.id]).toEqual(mockIssue2);
- expect(state.boardLists['gid://gitlab/List/1'].issuesSize).toBe(2);
+ expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(2);
});
});
@@ -524,6 +518,14 @@ describe('Board Store Mutations', () => {
});
});
+ describe('SET_ASSIGNEE_LOADING', () => {
+ it('sets isSettingAssignees to the value passed', () => {
+ mutations.SET_ASSIGNEE_LOADING(state, true);
+
+ expect(state.isSettingAssignees).toBe(true);
+ });
+ });
+
describe('SET_CURRENT_PAGE', () => {
expectNotImplemented(mutations.SET_CURRENT_PAGE);
});
diff --git a/spec/frontend/branches/ajax_loading_spinner_spec.js b/spec/frontend/branches/ajax_loading_spinner_spec.js
index a6404faa445..31cc7b99e42 100644
--- a/spec/frontend/branches/ajax_loading_spinner_spec.js
+++ b/spec/frontend/branches/ajax_loading_spinner_spec.js
@@ -9,7 +9,7 @@ describe('Ajax Loading Spinner', () => {
<a class="js-ajax-loading-spinner"
data-remote
href="http://goesnowhere.nothing/whereami">
- <i class="fa fa-trash-o"></i>
+ Remove me
</a></div>`;
AjaxLoadingSpinner.init();
ajaxLoadingSpinnerElement = document.querySelector('.js-ajax-loading-spinner');
diff --git a/spec/frontend/ci_lint/components/ci_lint_spec.js b/spec/frontend/ci_lint/components/ci_lint_spec.js
index b353da5910d..1c99fdb3505 100644
--- a/spec/frontend/ci_lint/components/ci_lint_spec.js
+++ b/spec/frontend/ci_lint/components/ci_lint_spec.js
@@ -3,8 +3,8 @@ import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import EditorLite from '~/vue_shared/components/editor_lite.vue';
import CiLint from '~/ci_lint/components/ci_lint.vue';
-import CiLintResults from '~/ci_lint/components/ci_lint_results.vue';
-import lintCIMutation from '~/ci_lint/graphql/mutations/lint_ci.mutation.graphql';
+import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
+import lintCIMutation from '~/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql';
import { mockLintDataValid } from '../mock_data';
describe('CI Lint', () => {
diff --git a/spec/frontend/ci_lint/graphql/resolvers_spec.js b/spec/frontend/ci_lint/graphql/resolvers_spec.js
deleted file mode 100644
index 437c52cf6b4..00000000000
--- a/spec/frontend/ci_lint/graphql/resolvers_spec.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import httpStatus from '~/lib/utils/http_status';
-
-import resolvers from '~/ci_lint/graphql/resolvers';
-import { mockLintResponse } from '../mock_data';
-
-describe('~/ci_lint/graphql/resolvers', () => {
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('Mutation', () => {
- describe('lintCI', () => {
- const endpoint = '/ci/lint';
-
- beforeEach(() => {
- mock.onPost(endpoint).reply(httpStatus.OK, mockLintResponse);
- });
-
- it('resolves lint data with type names', async () => {
- const result = resolvers.Mutation.lintCI(null, {
- endpoint,
- content: 'content',
- dry_run: true,
- });
-
- await expect(result).resolves.toMatchSnapshot();
- });
- });
- });
-});
diff --git a/spec/frontend/ci_lint/mock_data.js b/spec/frontend/ci_lint/mock_data.js
index b87c9f8413b..28ea0f55bf8 100644
--- a/spec/frontend/ci_lint/mock_data.js
+++ b/spec/frontend/ci_lint/mock_data.js
@@ -1,86 +1,4 @@
-export const mockLintResponse = {
- valid: true,
- errors: [],
- warnings: [],
- jobs: [
- {
- name: 'job_1',
- stage: 'test',
- before_script: ["echo 'before script 1'"],
- script: ["echo 'script 1'"],
- after_script: ["echo 'after script 1"],
- tag_list: ['tag 1'],
- environment: 'prd',
- when: 'on_success',
- allow_failure: false,
- only: null,
- except: { refs: ['master@gitlab-org/gitlab', '/^release/.*$/@gitlab-org/gitlab'] },
- },
- {
- name: 'job_2',
- stage: 'test',
- before_script: ["echo 'before script 2'"],
- script: ["echo 'script 2'"],
- after_script: ["echo 'after script 2"],
- tag_list: ['tag 2'],
- environment: 'stg',
- when: 'on_success',
- allow_failure: true,
- only: { refs: ['web', 'chat', 'pushes'] },
- except: { refs: ['master@gitlab-org/gitlab', '/^release/.*$/@gitlab-org/gitlab'] },
- },
- ],
-};
-
-export const mockJobs = [
- {
- name: 'job_1',
- stage: 'build',
- beforeScript: [],
- script: ["echo 'Building'"],
- afterScript: [],
- tagList: [],
- environment: null,
- when: 'on_success',
- allowFailure: true,
- only: { refs: ['web', 'chat', 'pushes'] },
- except: null,
- },
- {
- name: 'multi_project_job',
- stage: 'test',
- beforeScript: [],
- script: [],
- afterScript: [],
- tagList: [],
- environment: null,
- when: 'on_success',
- allowFailure: false,
- only: { refs: ['branches', 'tags'] },
- except: null,
- },
- {
- name: 'job_2',
- stage: 'test',
- beforeScript: ["echo 'before script'"],
- script: ["echo 'script'"],
- afterScript: ["echo 'after script"],
- tagList: [],
- environment: null,
- when: 'on_success',
- allowFailure: false,
- only: { refs: ['branches@gitlab-org/gitlab'] },
- except: { refs: ['master@gitlab-org/gitlab', '/^release/.*$/@gitlab-org/gitlab'] },
- },
-];
-
-export const mockErrors = [
- '"job_1 job: chosen stage does not exist; available stages are .pre, build, test, deploy, .post"',
-];
-
-export const mockWarnings = [
- '"jobs:multi_project_job may allow multiple pipelines to run for a single action due to `rules:when` clause with no `workflow:rules` - read more: https://docs.gitlab.com/ee/ci/troubleshooting.html#pipeline-warnings"',
-];
+import { mockJobs } from 'jest/pipeline_editor/mock_data';
export const mockLintDataValid = {
data: {
diff --git a/spec/frontend/close_reopen_report_toggle_spec.js b/spec/frontend/close_reopen_report_toggle_spec.js
deleted file mode 100644
index d2ce6298c5c..00000000000
--- a/spec/frontend/close_reopen_report_toggle_spec.js
+++ /dev/null
@@ -1,283 +0,0 @@
-import CloseReopenReportToggle from '~/close_reopen_report_toggle';
-import DropLab from '~/droplab/drop_lab';
-
-describe('CloseReopenReportToggle', () => {
- describe('class constructor', () => {
- const dropdownTrigger = {};
- const dropdownList = {};
- const button = {};
- let commentTypeToggle;
-
- beforeEach(() => {
- commentTypeToggle = new CloseReopenReportToggle({
- dropdownTrigger,
- dropdownList,
- button,
- });
- });
-
- it('sets .dropdownTrigger', () => {
- expect(commentTypeToggle.dropdownTrigger).toBe(dropdownTrigger);
- });
-
- it('sets .dropdownList', () => {
- expect(commentTypeToggle.dropdownList).toBe(dropdownList);
- });
-
- it('sets .button', () => {
- expect(commentTypeToggle.button).toBe(button);
- });
- });
-
- describe('initDroplab', () => {
- let closeReopenReportToggle;
- const dropdownList = {
- querySelector: jest.fn(),
- };
- const dropdownTrigger = {};
- const button = {};
- const reopenItem = {};
- const closeItem = {};
- const config = {};
-
- beforeEach(() => {
- jest.spyOn(DropLab.prototype, 'init').mockImplementation(() => {});
- dropdownList.querySelector.mockReturnValueOnce(reopenItem).mockReturnValueOnce(closeItem);
-
- closeReopenReportToggle = new CloseReopenReportToggle({
- dropdownTrigger,
- dropdownList,
- button,
- });
-
- jest.spyOn(closeReopenReportToggle, 'setConfig').mockReturnValue(config);
-
- closeReopenReportToggle.initDroplab();
- });
-
- it('sets .reopenItem and .closeItem', () => {
- expect(dropdownList.querySelector).toHaveBeenCalledWith('.reopen-item');
- expect(dropdownList.querySelector).toHaveBeenCalledWith('.close-item');
- expect(closeReopenReportToggle.reopenItem).toBe(reopenItem);
- expect(closeReopenReportToggle.closeItem).toBe(closeItem);
- });
-
- it('sets .droplab', () => {
- expect(closeReopenReportToggle.droplab).toEqual(expect.any(Object));
- });
-
- it('calls .setConfig', () => {
- expect(closeReopenReportToggle.setConfig).toHaveBeenCalled();
- });
-
- it('calls droplab.init', () => {
- expect(DropLab.prototype.init).toHaveBeenCalledWith(
- dropdownTrigger,
- dropdownList,
- expect.any(Array),
- config,
- );
- });
- });
-
- describe('updateButton', () => {
- let closeReopenReportToggle;
- const dropdownList = {};
- const dropdownTrigger = {};
- const button = {
- blur: jest.fn(),
- };
- const isClosed = true;
-
- beforeEach(() => {
- closeReopenReportToggle = new CloseReopenReportToggle({
- dropdownTrigger,
- dropdownList,
- button,
- });
-
- jest.spyOn(closeReopenReportToggle, 'toggleButtonType').mockImplementation(() => {});
-
- closeReopenReportToggle.updateButton(isClosed);
- });
-
- it('calls .toggleButtonType', () => {
- expect(closeReopenReportToggle.toggleButtonType).toHaveBeenCalledWith(isClosed);
- });
-
- it('calls .button.blur', () => {
- expect(closeReopenReportToggle.button.blur).toHaveBeenCalled();
- });
- });
-
- describe('toggleButtonType', () => {
- let closeReopenReportToggle;
- const dropdownList = {};
- const dropdownTrigger = {};
- const button = {};
- const isClosed = true;
- const showItem = {
- click: jest.fn(),
- };
- const hideItem = {};
- showItem.classList = {
- add: jest.fn(),
- remove: jest.fn(),
- };
- hideItem.classList = {
- add: jest.fn(),
- remove: jest.fn(),
- };
-
- beforeEach(() => {
- closeReopenReportToggle = new CloseReopenReportToggle({
- dropdownTrigger,
- dropdownList,
- button,
- });
-
- jest.spyOn(closeReopenReportToggle, 'getButtonTypes').mockReturnValue([showItem, hideItem]);
-
- closeReopenReportToggle.toggleButtonType(isClosed);
- });
-
- it('calls .getButtonTypes', () => {
- expect(closeReopenReportToggle.getButtonTypes).toHaveBeenCalledWith(isClosed);
- });
-
- it('removes hide class and add selected class to showItem, opposite for hideItem', () => {
- expect(showItem.classList.remove).toHaveBeenCalledWith('hidden');
- expect(showItem.classList.add).toHaveBeenCalledWith('droplab-item-selected');
- expect(hideItem.classList.add).toHaveBeenCalledWith('hidden');
- expect(hideItem.classList.remove).toHaveBeenCalledWith('droplab-item-selected');
- });
-
- it('clicks the showItem', () => {
- expect(showItem.click).toHaveBeenCalled();
- });
- });
-
- describe('getButtonTypes', () => {
- let closeReopenReportToggle;
- const dropdownList = {};
- const dropdownTrigger = {};
- const button = {};
- const reopenItem = {};
- const closeItem = {};
-
- beforeEach(() => {
- closeReopenReportToggle = new CloseReopenReportToggle({
- dropdownTrigger,
- dropdownList,
- button,
- });
-
- closeReopenReportToggle.reopenItem = reopenItem;
- closeReopenReportToggle.closeItem = closeItem;
- });
-
- it('returns reopenItem, closeItem if isClosed is true', () => {
- const buttonTypes = closeReopenReportToggle.getButtonTypes(true);
-
- expect(buttonTypes).toEqual([reopenItem, closeItem]);
- });
-
- it('returns closeItem, reopenItem if isClosed is false', () => {
- const buttonTypes = closeReopenReportToggle.getButtonTypes(false);
-
- expect(buttonTypes).toEqual([closeItem, reopenItem]);
- });
- });
-
- describe('setDisable', () => {
- let closeReopenReportToggle;
- const dropdownList = {};
- const dropdownTrigger = {
- setAttribute: jest.fn(),
- removeAttribute: jest.fn(),
- };
- const button = {
- setAttribute: jest.fn(),
- removeAttribute: jest.fn(),
- };
-
- beforeEach(() => {
- closeReopenReportToggle = new CloseReopenReportToggle({
- dropdownTrigger,
- dropdownList,
- button,
- });
- });
-
- it('disable .button and .dropdownTrigger if shouldDisable is true', () => {
- closeReopenReportToggle.setDisable(true);
-
- expect(button.setAttribute).toHaveBeenCalledWith('disabled', 'true');
- expect(dropdownTrigger.setAttribute).toHaveBeenCalledWith('disabled', 'true');
- });
-
- it('disable .button and .dropdownTrigger if shouldDisable is undefined', () => {
- closeReopenReportToggle.setDisable();
-
- expect(button.setAttribute).toHaveBeenCalledWith('disabled', 'true');
- expect(dropdownTrigger.setAttribute).toHaveBeenCalledWith('disabled', 'true');
- });
-
- it('enable .button and .dropdownTrigger if shouldDisable is false', () => {
- closeReopenReportToggle.setDisable(false);
-
- expect(button.removeAttribute).toHaveBeenCalledWith('disabled');
- expect(dropdownTrigger.removeAttribute).toHaveBeenCalledWith('disabled');
- });
- });
-
- describe('setConfig', () => {
- let closeReopenReportToggle;
- const dropdownList = {};
- const dropdownTrigger = {};
- const button = {};
- let config;
-
- beforeEach(() => {
- closeReopenReportToggle = new CloseReopenReportToggle({
- dropdownTrigger,
- dropdownList,
- button,
- });
-
- config = closeReopenReportToggle.setConfig();
- });
-
- it('returns a config object', () => {
- expect(config).toEqual({
- InputSetter: [
- {
- input: button,
- valueAttribute: 'data-text',
- inputAttribute: 'data-value',
- },
- {
- input: button,
- valueAttribute: 'data-text',
- inputAttribute: 'title',
- },
- {
- input: button,
- valueAttribute: 'data-button-class',
- inputAttribute: 'class',
- },
- {
- input: dropdownTrigger,
- valueAttribute: 'data-toggle-class',
- inputAttribute: 'class',
- },
- {
- input: button,
- valueAttribute: 'data-url',
- inputAttribute: 'data-endpoint',
- },
- ],
- });
- });
- });
-});
diff --git a/spec/frontend/clusters/components/__snapshots__/applications_spec.js.snap b/spec/frontend/clusters/components/__snapshots__/applications_spec.js.snap
index 744ef318260..c2ace1b4e30 100644
--- a/spec/frontend/clusters/components/__snapshots__/applications_spec.js.snap
+++ b/spec/frontend/clusters/components/__snapshots__/applications_spec.js.snap
@@ -55,7 +55,7 @@ exports[`Applications Crossplane application shows the correct description 1`] =
`;
exports[`Applications Ingress application shows the correct warning message 1`] = `
-<strong
+<span
data-testid="ingressCostWarning"
>
Installing Ingress may incur additional costs. Learn more about
@@ -68,7 +68,7 @@ exports[`Applications Ingress application shows the correct warning message 1`]
pricing
</a>
.
-</strong>
+</span>
`;
exports[`Applications Knative application shows the correct description 1`] = `
diff --git a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
index de40e03b598..6f28573c808 100644
--- a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
+++ b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
@@ -53,6 +53,7 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
type="button"
>
<svg
+ aria-hidden="true"
class="gl-icon s16 gl-new-dropdown-item-check-icon"
data-testid="mobile-issue-close-icon"
>
@@ -107,6 +108,7 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
type="button"
>
<svg
+ aria-hidden="true"
class="gl-icon s16 gl-new-dropdown-item-check-icon gl-visibility-hidden"
data-testid="mobile-issue-close-icon"
>
diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js
index ed862818c7b..381a4717127 100644
--- a/spec/frontend/clusters/stores/clusters_store_spec.js
+++ b/spec/frontend/clusters/stores/clusters_store_spec.js
@@ -50,6 +50,7 @@ describe('Clusters Store', () => {
expect(store.state).toEqual({
helpPath: null,
+ helmHelpPath: null,
ingressHelpPath: null,
environmentsHelpPath: null,
clustersHelpPath: null,
@@ -62,7 +63,7 @@ describe('Clusters Store', () => {
rbac: false,
applications: {
helm: {
- title: 'Helm Tiller',
+ title: 'Legacy Helm Tiller server',
status: mockResponseData.applications[0].status,
statusReason: mockResponseData.applications[0].status_reason,
requestReason: null,
diff --git a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
index 62e527a2c5f..b59d1597a12 100644
--- a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
+++ b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
@@ -17,6 +17,7 @@ exports[`Code navigation popover component renders popover 1`] = `
>
<gl-tab-stub
title="Definition"
+ titlelinkclass=""
>
<div
class="overflow-auto code-navigation-popover-container"
@@ -76,6 +77,7 @@ exports[`Code navigation popover component renders popover 1`] = `
<gl-tab-stub
class="py-2"
data-testid="references-tab"
+ titlelinkclass=""
>
<p
diff --git a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js
index f12f300872a..f14a555f357 100644
--- a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js
+++ b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js
@@ -186,7 +186,7 @@ describe('EKS Cluster Store Actions', () => {
role_external_id: payload.externalId,
region: DEFAULT_REGION,
})
- .reply(400, error);
+ .reply(400, null);
});
it('dispatches createRoleError action', () =>
@@ -198,6 +198,32 @@ describe('EKS Cluster Store Actions', () => {
[{ type: 'requestCreateRole' }, { type: 'createRoleError', payload: { error } }],
));
});
+
+ describe('when request fails with a message', () => {
+ beforeEach(() => {
+ const errResp = { message: 'Something failed' };
+
+ mock
+ .onPost(state.createRolePath, {
+ role_arn: payload.roleArn,
+ role_external_id: payload.externalId,
+ region: DEFAULT_REGION,
+ })
+ .reply(4, errResp);
+ });
+
+ it('dispatches createRoleError action', () =>
+ testAction(
+ actions.createRole,
+ payload,
+ state,
+ [],
+ [
+ { type: 'requestCreateRole' },
+ { type: 'createRoleError', payload: { error: 'Something failed' } },
+ ],
+ ));
+ });
});
describe('requestCreateRole', () => {
diff --git a/spec/frontend/cycle_analytics/banner_spec.js b/spec/frontend/cycle_analytics/banner_spec.js
index f0b8cb18a90..0cae0298cee 100644
--- a/spec/frontend/cycle_analytics/banner_spec.js
+++ b/spec/frontend/cycle_analytics/banner_spec.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import banner from '~/cycle_analytics/components/banner.vue';
-describe('Cycle analytics banner', () => {
+describe('Value Stream Analytics banner', () => {
let vm;
beforeEach(() => {
diff --git a/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap b/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
index 1e94e90c3b0..8c6b446794f 100644
--- a/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
+++ b/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
@@ -2,7 +2,7 @@
exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
<gl-dropdown-stub
- category="tertiary"
+ category="primary"
headertext=""
issueiid=""
projectpath=""
@@ -14,6 +14,7 @@ exports[`Design management design version dropdown component renders design vers
avatarurl=""
iconcolor=""
iconname=""
+ iconrightarialabel=""
iconrightname=""
ischecked="true"
ischeckitem="true"
@@ -27,6 +28,7 @@ exports[`Design management design version dropdown component renders design vers
avatarurl=""
iconcolor=""
iconname=""
+ iconrightarialabel=""
iconrightname=""
ischeckitem="true"
secondarytext=""
@@ -40,7 +42,7 @@ exports[`Design management design version dropdown component renders design vers
exports[`Design management design version dropdown component renders design version list 1`] = `
<gl-dropdown-stub
- category="tertiary"
+ category="primary"
headertext=""
issueiid=""
projectpath=""
@@ -52,6 +54,7 @@ exports[`Design management design version dropdown component renders design vers
avatarurl=""
iconcolor=""
iconname=""
+ iconrightarialabel=""
iconrightname=""
ischecked="true"
ischeckitem="true"
@@ -65,6 +68,7 @@ exports[`Design management design version dropdown component renders design vers
avatarurl=""
iconcolor=""
iconname=""
+ iconrightarialabel=""
iconrightname=""
ischeckitem="true"
secondarytext=""
diff --git a/spec/frontend/design_management/pages/design/index_spec.js b/spec/frontend/design_management/pages/design/index_spec.js
index 88892bb1878..9c11af28cf0 100644
--- a/spec/frontend/design_management/pages/design/index_spec.js
+++ b/spec/frontend/design_management/pages/design/index_spec.js
@@ -2,7 +2,9 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueRouter from 'vue-router';
import { GlAlert } from '@gitlab/ui';
import { ApolloMutation } from 'vue-apollo';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import createFlash from '~/flash';
+import Api from '~/api';
import DesignIndex from '~/design_management/pages/design/index.vue';
import DesignSidebar from '~/design_management/components/design_sidebar.vue';
import DesignPresentation from '~/design_management/components/design_presentation.vue';
@@ -20,8 +22,14 @@ import design from '../../mock_data/design';
import mockResponseWithDesigns from '../../mock_data/designs';
import mockResponseNoDesigns from '../../mock_data/no_designs';
import mockAllVersions from '../../mock_data/all_versions';
+import {
+ DESIGN_TRACKING_PAGE_NAME,
+ DESIGN_SNOWPLOW_EVENT_TYPES,
+ DESIGN_USAGE_PING_EVENT_TYPES,
+} from '~/design_management/utils/tracking';
jest.mock('~/flash');
+jest.mock('~/api.js');
const focusInput = jest.fn();
const mutate = jest.fn().mockResolvedValue();
@@ -81,7 +89,10 @@ describe('Design management design index page', () => {
const findSidebar = () => wrapper.find(DesignSidebar);
const findDesignPresentation = () => wrapper.find(DesignPresentation);
- function createComponent({ loading = false } = {}, { data = {}, intialRouteOptions = {} } = {}) {
+ function createComponent(
+ { loading = false } = {},
+ { data = {}, intialRouteOptions = {}, provide = {} } = {},
+ ) {
const $apollo = {
queries: {
design: {
@@ -106,6 +117,7 @@ describe('Design management design index page', () => {
provide: {
issueIid: '1',
projectPath: 'project-path',
+ ...provide,
},
data() {
return {
@@ -343,4 +355,64 @@ describe('Design management design index page', () => {
});
});
});
+
+ describe('tracking', () => {
+ let trackingSpy;
+
+ beforeEach(() => {
+ trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ describe('on mount', () => {
+ it('tracks design view in snowplow', () => {
+ createComponent({ loading: true });
+
+ expect(trackingSpy).toHaveBeenCalledTimes(1);
+ expect(trackingSpy).toHaveBeenCalledWith(
+ DESIGN_TRACKING_PAGE_NAME,
+ DESIGN_SNOWPLOW_EVENT_TYPES.VIEW_DESIGN,
+ {
+ context: {
+ data: {
+ 'design-collection-owner': 'issue',
+ 'design-is-current-version': true,
+ 'design-version-number': 1,
+ 'internal-object-referrer': 'issue-design-collection',
+ },
+ schema: 'iglu:com.gitlab/design_management_context/jsonschema/1-0-0',
+ },
+ label: DESIGN_SNOWPLOW_EVENT_TYPES.VIEW_DESIGN,
+ },
+ );
+ });
+
+ describe('with usage_data_design_action enabled', () => {
+ it('tracks design view usage ping', () => {
+ createComponent(
+ { loading: true },
+ {
+ provide: {
+ glFeatures: { usageDataDesignAction: true },
+ },
+ },
+ );
+ expect(Api.trackRedisHllUserEvent).toHaveBeenCalledTimes(1);
+ expect(Api.trackRedisHllUserEvent).toHaveBeenCalledWith(
+ DESIGN_USAGE_PING_EVENT_TYPES.DESIGN_ACTION,
+ );
+ });
+ });
+
+ describe('with usage_data_design_action disabled', () => {
+ it("doesn't track design view usage ping", () => {
+ createComponent({ loading: true });
+ expect(Api.trackRedisHllUserEvent).toHaveBeenCalledTimes(0);
+ });
+ });
+ });
+ });
});
diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js
index 05238efd761..147169dd9aa 100644
--- a/spec/frontend/design_management/pages/index_spec.js
+++ b/spec/frontend/design_management/pages/index_spec.js
@@ -31,7 +31,10 @@ import {
moveDesignMutationResponseWithErrors,
} from '../mock_data/apollo_mock';
import moveDesignMutation from '~/design_management/graphql/mutations/move_design.mutation.graphql';
-import { DESIGN_TRACKING_PAGE_NAME } from '~/design_management/utils/tracking';
+import {
+ DESIGN_TRACKING_PAGE_NAME,
+ DESIGN_SNOWPLOW_EVENT_TYPES,
+} from '~/design_management/utils/tracking';
jest.mock('~/flash.js');
const mockPageEl = {
@@ -509,14 +512,20 @@ describe('Design management index page', () => {
wrapper.vm.onUploadDesignDone(designUploadMutationCreatedResponse);
expect(trackingSpy).toHaveBeenCalledTimes(1);
- expect(trackingSpy).toHaveBeenCalledWith(DESIGN_TRACKING_PAGE_NAME, 'create_design');
+ expect(trackingSpy).toHaveBeenCalledWith(
+ DESIGN_TRACKING_PAGE_NAME,
+ DESIGN_SNOWPLOW_EVENT_TYPES.CREATE_DESIGN,
+ );
});
it('tracks design modification', () => {
wrapper.vm.onUploadDesignDone(designUploadMutationUpdatedResponse);
expect(trackingSpy).toHaveBeenCalledTimes(1);
- expect(trackingSpy).toHaveBeenCalledWith(DESIGN_TRACKING_PAGE_NAME, 'update_design');
+ expect(trackingSpy).toHaveBeenCalledWith(
+ DESIGN_TRACKING_PAGE_NAME,
+ DESIGN_SNOWPLOW_EVENT_TYPES.UPDATE_DESIGN,
+ );
});
});
});
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index 225710eab63..416564b72c3 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -12,16 +12,19 @@ import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
import CollapsedFilesWarning from '~/diffs/components/collapsed_files_warning.vue';
import CommitWidget from '~/diffs/components/commit_widget.vue';
import TreeList from '~/diffs/components/tree_list.vue';
-import { INLINE_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE } from '~/diffs/constants';
import createDiffsStore from '../create_diffs_store';
import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import diffsMockData from '../mock_data/merge_request_diffs';
+import { EVT_VIEW_FILE_BY_FILE } from '~/diffs/constants';
+
+import eventHub from '~/diffs/event_hub';
+
const mergeRequestDiff = { version_index: 1 };
const TEST_ENDPOINT = `${TEST_HOST}/diff/endpoint`;
-const COMMIT_URL = '[BASE URL]/OLD';
-const UPDATED_COMMIT_URL = '[BASE URL]/NEW';
+const COMMIT_URL = `${TEST_HOST}/COMMIT/OLD`;
+const UPDATED_COMMIT_URL = `${TEST_HOST}/COMMIT/NEW`;
function getCollapsedFilesWarning(wrapper) {
return wrapper.find(CollapsedFilesWarning);
@@ -62,7 +65,7 @@ describe('diffs/components/app', () => {
changesEmptyStateIllustration: '',
dismissEndpoint: '',
showSuggestPopover: true,
- viewDiffsFileByFile: false,
+ fileByFileUserPreference: false,
...props,
},
provide,
@@ -75,12 +78,6 @@ describe('diffs/components/app', () => {
});
}
- function getOppositeViewType(currentViewType) {
- return currentViewType === INLINE_DIFF_VIEW_TYPE
- ? PARALLEL_DIFF_VIEW_TYPE
- : INLINE_DIFF_VIEW_TYPE;
- }
-
beforeEach(() => {
// setup globals (needed for component to mount :/)
window.mrTabs = {
@@ -125,104 +122,6 @@ describe('diffs/components/app', () => {
wrapper.vm.$nextTick(done);
});
- describe('when the diff view type changes and it should load a single diff view style', () => {
- const noLinesDiff = {
- highlighted_diff_lines: [],
- parallel_diff_lines: [],
- };
- const parallelLinesDiff = {
- highlighted_diff_lines: [],
- parallel_diff_lines: ['line'],
- };
- const inlineLinesDiff = {
- highlighted_diff_lines: ['line'],
- parallel_diff_lines: [],
- };
- const fullDiff = {
- highlighted_diff_lines: ['line'],
- parallel_diff_lines: ['line'],
- };
-
- function expectFetchToOccur({ vueInstance, done = () => {}, existingFiles = 1 } = {}) {
- vueInstance.$nextTick(() => {
- expect(vueInstance.diffFiles.length).toEqual(existingFiles);
- expect(vueInstance.fetchDiffFilesBatch).toHaveBeenCalled();
-
- done();
- });
- }
-
- it('fetches diffs if it has none', done => {
- wrapper.vm.isLatestVersion = () => false;
-
- store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
-
- expectFetchToOccur({ vueInstance: wrapper.vm, existingFiles: 0, done });
- });
-
- it('fetches diffs if it has both view styles, but no lines in either', done => {
- wrapper.vm.isLatestVersion = () => false;
-
- store.state.diffs.diffFiles.push(noLinesDiff);
- store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
-
- expectFetchToOccur({ vueInstance: wrapper.vm, done });
- });
-
- it('fetches diffs if it only has inline view style', done => {
- wrapper.vm.isLatestVersion = () => false;
-
- store.state.diffs.diffFiles.push(inlineLinesDiff);
- store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
-
- expectFetchToOccur({ vueInstance: wrapper.vm, done });
- });
-
- it('fetches diffs if it only has parallel view style', done => {
- wrapper.vm.isLatestVersion = () => false;
-
- store.state.diffs.diffFiles.push(parallelLinesDiff);
- store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
-
- expectFetchToOccur({ vueInstance: wrapper.vm, done });
- });
-
- it('fetches batch diffs if it has none', done => {
- store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
-
- expectFetchToOccur({ vueInstance: wrapper.vm, existingFiles: 0, done });
- });
-
- it('fetches batch diffs if it has both view styles, but no lines in either', done => {
- store.state.diffs.diffFiles.push(noLinesDiff);
- store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
-
- expectFetchToOccur({ vueInstance: wrapper.vm, done });
- });
-
- it('fetches batch diffs if it only has inline view style', done => {
- store.state.diffs.diffFiles.push(inlineLinesDiff);
- store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
-
- expectFetchToOccur({ vueInstance: wrapper.vm, done });
- });
-
- it('fetches batch diffs if it only has parallel view style', done => {
- store.state.diffs.diffFiles.push(parallelLinesDiff);
- store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
-
- expectFetchToOccur({ vueInstance: wrapper.vm, done });
- });
-
- it('does not fetch batch diffs if it has already fetched both styles of diff', () => {
- store.state.diffs.diffFiles.push(fullDiff);
- store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
-
- expect(wrapper.vm.diffFiles.length).toEqual(1);
- expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled();
- });
- });
-
it('calls batch methods if diffsBatchLoad is enabled, and not latest version', done => {
expect(wrapper.vm.diffFilesLength).toEqual(0);
wrapper.vm.isLatestVersion = () => false;
@@ -743,70 +642,76 @@ describe('diffs/components/app', () => {
});
});
- describe('hideTreeListIfJustOneFile', () => {
- let toggleShowTreeList;
+ describe('setTreeDisplay', () => {
+ let setShowTreeList;
beforeEach(() => {
- toggleShowTreeList = jest.fn();
+ setShowTreeList = jest.fn();
});
afterEach(() => {
localStorage.removeItem('mr_tree_show');
});
- it('calls toggleShowTreeList when only 1 file', () => {
+ it('calls setShowTreeList when only 1 file', () => {
createComponent({}, ({ state }) => {
state.diffs.diffFiles.push({ sha: '123' });
});
wrapper.setMethods({
- toggleShowTreeList,
+ setShowTreeList,
});
- wrapper.vm.hideTreeListIfJustOneFile();
+ wrapper.vm.setTreeDisplay();
- expect(toggleShowTreeList).toHaveBeenCalledWith(false);
+ expect(setShowTreeList).toHaveBeenCalledWith({ showTreeList: false, saving: false });
});
- it('does not call toggleShowTreeList when more than 1 file', () => {
+ it('calls setShowTreeList with true when more than 1 file is in diffs array', () => {
createComponent({}, ({ state }) => {
state.diffs.diffFiles.push({ sha: '123' });
state.diffs.diffFiles.push({ sha: '124' });
});
wrapper.setMethods({
- toggleShowTreeList,
+ setShowTreeList,
});
- wrapper.vm.hideTreeListIfJustOneFile();
+ wrapper.vm.setTreeDisplay();
- expect(toggleShowTreeList).not.toHaveBeenCalled();
+ expect(setShowTreeList).toHaveBeenCalledWith({ showTreeList: true, saving: false });
});
- it('does not call toggleShowTreeList when localStorage is set', () => {
- localStorage.setItem('mr_tree_show', 'true');
+ it.each`
+ showTreeList
+ ${true}
+ ${false}
+ `('calls setShowTreeList with localstorage $showTreeList', ({ showTreeList }) => {
+ localStorage.setItem('mr_tree_show', showTreeList);
createComponent({}, ({ state }) => {
state.diffs.diffFiles.push({ sha: '123' });
});
wrapper.setMethods({
- toggleShowTreeList,
+ setShowTreeList,
});
- wrapper.vm.hideTreeListIfJustOneFile();
+ wrapper.vm.setTreeDisplay();
- expect(toggleShowTreeList).not.toHaveBeenCalled();
+ expect(setShowTreeList).toHaveBeenCalledWith({ showTreeList, saving: false });
});
});
describe('file-by-file', () => {
- it('renders a single diff', () => {
- createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ it('renders a single diff', async () => {
+ createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' });
state.diffs.diffFiles.push({ file_hash: '312' });
});
+ await wrapper.vm.$nextTick();
+
expect(wrapper.findAll(DiffFile).length).toBe(1);
});
@@ -814,31 +719,37 @@ describe('diffs/components/app', () => {
const fileByFileNav = () => wrapper.find('[data-testid="file-by-file-navigation"]');
const paginator = () => fileByFileNav().find(GlPagination);
- it('sets previous button as disabled', () => {
- createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ it('sets previous button as disabled', async () => {
+ createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
});
+ await wrapper.vm.$nextTick();
+
expect(paginator().attributes('prevpage')).toBe(undefined);
expect(paginator().attributes('nextpage')).toBe('2');
});
- it('sets next button as disabled', () => {
- createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ it('sets next button as disabled', async () => {
+ createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
state.diffs.currentDiffFileId = '312';
});
+ await wrapper.vm.$nextTick();
+
expect(paginator().attributes('prevpage')).toBe('1');
expect(paginator().attributes('nextpage')).toBe(undefined);
});
- it("doesn't display when there's fewer than 2 files", () => {
- createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ it("doesn't display when there's fewer than 2 files", async () => {
+ createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' });
state.diffs.currentDiffFileId = '123';
});
+ await wrapper.vm.$nextTick();
+
expect(fileByFileNav().exists()).toBe(false);
});
@@ -849,11 +760,13 @@ describe('diffs/components/app', () => {
`(
'it calls navigateToDiffFileIndex with $index when $link is clicked',
async ({ currentDiffFileId, targetFile }) => {
- createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
state.diffs.currentDiffFileId = currentDiffFileId;
});
+ await wrapper.vm.$nextTick();
+
jest.spyOn(wrapper.vm, 'navigateToDiffFileIndex');
paginator().vm.$emit('input', targetFile);
@@ -864,5 +777,24 @@ describe('diffs/components/app', () => {
},
);
});
+
+ describe('control via event stream', () => {
+ it.each`
+ setting
+ ${true}
+ ${false}
+ `(
+ 'triggers the action with the new fileByFile setting - $setting - when the event with that setting is received',
+ async ({ setting }) => {
+ createComponent();
+ await wrapper.vm.$nextTick();
+
+ eventHub.$emit(EVT_VIEW_FILE_BY_FILE, { setting });
+ await wrapper.vm.$nextTick();
+
+ expect(store.state.diffs.viewDiffsFileByFile).toBe(setting);
+ },
+ );
+ });
});
});
diff --git a/spec/frontend/diffs/components/commit_item_spec.js b/spec/frontend/diffs/components/commit_item_spec.js
index 9e4fcddd1b4..8a7eb6aaca6 100644
--- a/spec/frontend/diffs/components/commit_item_spec.js
+++ b/spec/frontend/diffs/components/commit_item_spec.js
@@ -84,7 +84,7 @@ describe('diffs/components/commit_item', () => {
it('renders commit sha', () => {
const shaElement = getShaElement();
- const labelElement = shaElement.find('[data-testid="commit-sha-group"] button');
+ const labelElement = shaElement.find('[data-testid="commit-sha-short-id"]');
const buttonElement = shaElement.find('button.input-group-text');
expect(labelElement.text()).toEqual(commit.short_id);
diff --git a/spec/frontend/diffs/components/compare_dropdown_layout_spec.js b/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
index a163a43daf1..92e4a2d9c62 100644
--- a/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
+++ b/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
@@ -1,4 +1,4 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import CompareDropdownLayout from '~/diffs/components/compare_dropdown_layout.vue';
@@ -22,7 +22,7 @@ describe('CompareDropdownLayout', () => {
});
const createComponent = (propsData = {}) => {
- wrapper = shallowMount(CompareDropdownLayout, {
+ wrapper = mount(CompareDropdownLayout, {
propsData: {
...propsData,
},
@@ -35,7 +35,7 @@ describe('CompareDropdownLayout', () => {
href: listItem.find('a').attributes('href'),
text: trimText(listItem.text()),
createdAt: listItem.findAll(TimeAgo).wrappers[0]?.props('time'),
- isActive: listItem.find('a.is-active').exists(),
+ isActive: listItem.classes().includes('is-active'),
}));
afterEach(() => {
@@ -69,7 +69,7 @@ describe('CompareDropdownLayout', () => {
expect(findListItemsData()).toEqual([
{
href: 'version/1',
- text: 'version 1 (base) abcdef1 1 commit',
+ text: 'version 1 (base) abcdef1 1 commit 2 years ago',
createdAt: TEST_CREATED_AT,
isActive: true,
},
diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js
index b3dfc71260c..09e9669c474 100644
--- a/spec/frontend/diffs/components/compare_versions_spec.js
+++ b/spec/frontend/diffs/components/compare_versions_spec.js
@@ -59,8 +59,8 @@ describe('CompareVersions', () => {
expect(sourceDropdown.exists()).toBe(true);
expect(targetDropdown.exists()).toBe(true);
- expect(sourceDropdown.find('a span').html()).toContain('latest version');
- expect(targetDropdown.find('a span').html()).toContain(targetBranchName);
+ expect(sourceDropdown.find('a p').html()).toContain('latest version');
+ expect(targetDropdown.find('button').html()).toContain(targetBranchName);
});
it('should not render comparison dropdowns if no mergeRequestDiffs are specified', () => {
diff --git a/spec/frontend/diffs/components/diff_content_spec.js b/spec/frontend/diffs/components/diff_content_spec.js
index e3a6f7f16a9..43d295ff1b3 100644
--- a/spec/frontend/diffs/components/diff_content_spec.js
+++ b/spec/frontend/diffs/components/diff_content_spec.js
@@ -6,13 +6,11 @@ import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
-import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
import NoteForm from '~/notes/components/note_form.vue';
import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import diffFileMockData from '../mock_data/diff_file';
import { diffViewerModes } from '~/ide/constants';
-import { diffLines } from '~/diffs/store/getters';
import DiffView from '~/diffs/components/diff_view.vue';
const localVue = createLocalVue();
@@ -74,7 +72,7 @@ describe('DiffContent', () => {
isInlineView: isInlineViewGetterMock,
isParallelView: isParallelViewGetterMock,
getCommentFormForDiffFile: getCommentFormForDiffFileGetterMock,
- diffLines,
+ diffLines: () => () => [...diffFileMockData.parallel_diff_lines],
},
actions: {
saveDiffDiscussion: saveDiffDiscussionMock,
@@ -122,11 +120,11 @@ describe('DiffContent', () => {
expect(wrapper.find(ParallelDiffView).exists()).toBe(true);
});
- it('should render diff view if `unifiedDiffLines` & `unifiedDiffComponents` are true', () => {
+ it('should render diff view if `unifiedDiffComponents` are true', () => {
isParallelViewGetterMock.mockReturnValue(true);
createComponent({
props: { diffFile: textDiffFile },
- provide: { glFeatures: { unifiedDiffLines: true, unifiedDiffComponents: true } },
+ provide: { glFeatures: { unifiedDiffComponents: true } },
});
expect(wrapper.find(DiffView).exists()).toBe(true);
@@ -167,14 +165,6 @@ describe('DiffContent', () => {
describe('with image files', () => {
const imageDiffFile = { ...defaultProps.diffFile, viewer: { name: diffViewerModes.image } };
- it('should have image diff view in place', () => {
- getCommentFormForDiffFileGetterMock.mockReturnValue(() => true);
- createComponent({ props: { diffFile: imageDiffFile } });
-
- expect(wrapper.find(InlineDiffView).exists()).toBe(false);
- expect(wrapper.find(ImageDiffOverlay).exists()).toBe(true);
- });
-
it('renders diff file discussions', () => {
getCommentFormForDiffFileGetterMock.mockReturnValue(() => true);
createComponent({
diff --git a/spec/frontend/diffs/components/diff_expansion_cell_spec.js b/spec/frontend/diffs/components/diff_expansion_cell_spec.js
index 81e08f09f62..a3b4b5c3abb 100644
--- a/spec/frontend/diffs/components/diff_expansion_cell_spec.js
+++ b/spec/frontend/diffs/components/diff_expansion_cell_spec.js
@@ -5,18 +5,16 @@ import { getByText } from '@testing-library/dom';
import { createStore } from '~/mr_notes/stores';
import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue';
import { getPreviousLineIndex } from '~/diffs/store/utils';
-import { INLINE_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE } from '~/diffs/constants';
+import { INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
import diffFileMockData from '../mock_data/diff_file';
const EXPAND_UP_CLASS = '.js-unfold';
const EXPAND_DOWN_CLASS = '.js-unfold-down';
const lineSources = {
[INLINE_DIFF_VIEW_TYPE]: 'highlighted_diff_lines',
- [PARALLEL_DIFF_VIEW_TYPE]: 'parallel_diff_lines',
};
const lineHandlers = {
[INLINE_DIFF_VIEW_TYPE]: line => line,
- [PARALLEL_DIFF_VIEW_TYPE]: line => line.right || line.left,
};
function makeLoadMoreLinesPayload({
@@ -126,7 +124,6 @@ describe('DiffExpansionCell', () => {
describe('any row', () => {
[
{ diffViewType: INLINE_DIFF_VIEW_TYPE, lineIndex: 8, file: { parallel_diff_lines: [] } },
- { diffViewType: PARALLEL_DIFF_VIEW_TYPE, lineIndex: 7, file: { highlighted_diff_lines: [] } },
].forEach(({ diffViewType, file, lineIndex }) => {
describe(`with diffViewType (${diffViewType})`, () => {
beforeEach(() => {
diff --git a/spec/frontend/diffs/components/diff_file_row_spec.js b/spec/frontend/diffs/components/diff_file_row_spec.js
index 23adc8f9da4..7403a7918a9 100644
--- a/spec/frontend/diffs/components/diff_file_row_spec.js
+++ b/spec/frontend/diffs/components/diff_file_row_spec.js
@@ -7,12 +7,9 @@ import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
describe('Diff File Row component', () => {
let wrapper;
- const createComponent = (props = {}, highlightCurrentDiffRow = false) => {
+ const createComponent = (props = {}) => {
wrapper = shallowMount(DiffFileRow, {
propsData: { ...props },
- provide: {
- glFeatures: { highlightCurrentDiffRow },
- },
});
};
@@ -60,26 +57,23 @@ describe('Diff File Row component', () => {
});
it.each`
- features | fileType | isViewed | expected
- ${{ highlightCurrentDiffRow: true }} | ${'blob'} | ${false} | ${'gl-font-weight-bold'}
- ${{}} | ${'blob'} | ${true} | ${''}
- ${{}} | ${'tree'} | ${false} | ${''}
- ${{}} | ${'tree'} | ${true} | ${''}
+ fileType | isViewed | expected
+ ${'blob'} | ${false} | ${'gl-font-weight-bold'}
+ ${'blob'} | ${true} | ${''}
+ ${'tree'} | ${false} | ${''}
+ ${'tree'} | ${true} | ${''}
`(
- 'with (features="$features", fileType="$fileType", isViewed=$isViewed), sets fileClasses="$expected"',
- ({ features, fileType, isViewed, expected }) => {
- createComponent(
- {
- file: {
- type: fileType,
- fileHash: '#123456789',
- },
- level: 0,
- hideFileStats: false,
- viewedFiles: isViewed ? { '#123456789': true } : {},
+ 'with (fileType="$fileType", isViewed=$isViewed), sets fileClasses="$expected"',
+ ({ fileType, isViewed, expected }) => {
+ createComponent({
+ file: {
+ type: fileType,
+ fileHash: '#123456789',
},
- features.highlightCurrentDiffRow,
- );
+ level: 0,
+ hideFileStats: false,
+ viewedFiles: isViewed ? { '#123456789': true } : {},
+ });
expect(wrapper.find(FileRow).props('fileClasses')).toBe(expected);
},
);
diff --git a/spec/frontend/diffs/components/diff_row_spec.js b/spec/frontend/diffs/components/diff_row_spec.js
index f9e76cf8107..0ec075c8ad8 100644
--- a/spec/frontend/diffs/components/diff_row_spec.js
+++ b/spec/frontend/diffs/components/diff_row_spec.js
@@ -97,18 +97,18 @@ describe('DiffRow', () => {
${'right'}
`('$side side', ({ side }) => {
it(`renders empty cells if ${side} is unavailable`, () => {
- const wrapper = createWrapper({ props: { line: testLines[2] } });
+ const wrapper = createWrapper({ props: { line: testLines[2], inline: false } });
expect(wrapper.find(`[data-testid="${side}LineNumber"]`).exists()).toBe(false);
expect(wrapper.find(`[data-testid="${side}EmptyCell"]`).exists()).toBe(true);
});
it('renders comment button', () => {
- const wrapper = createWrapper({ props: { line: testLines[3] } });
+ const wrapper = createWrapper({ props: { line: testLines[3], inline: false } });
expect(wrapper.find(`[data-testid="${side}CommentButton"]`).exists()).toBe(true);
});
it('renders avatars', () => {
- const wrapper = createWrapper({ props: { line: testLines[0] } });
+ const wrapper = createWrapper({ props: { line: testLines[0], inline: false } });
expect(wrapper.find(`[data-testid="${side}Discussions"]`).exists()).toBe(true);
});
});
diff --git a/spec/frontend/diffs/components/image_diff_overlay_spec.js b/spec/frontend/diffs/components/image_diff_overlay_spec.js
index 5a88a3cabd1..087715111b4 100644
--- a/spec/frontend/diffs/components/image_diff_overlay_spec.js
+++ b/spec/frontend/diffs/components/image_diff_overlay_spec.js
@@ -24,6 +24,8 @@ describe('Diffs image diff overlay component', () => {
propsData: {
discussions: [...imageDiffDiscussions],
fileHash: 'ABC',
+ renderedWidth: 200,
+ renderedHeight: 200,
...props,
},
methods: {
@@ -71,8 +73,8 @@ describe('Diffs image diff overlay component', () => {
createComponent();
const imageBadges = getAllImageBadges();
- expect(imageBadges.at(0).attributes('style')).toBe('left: 10px; top: 10px;');
- expect(imageBadges.at(1).attributes('style')).toBe('left: 5px; top: 5px;');
+ expect(imageBadges.at(0).attributes('style')).toBe('left: 10%; top: 5%;');
+ expect(imageBadges.at(1).attributes('style')).toBe('left: 5%; top: 2.5%;');
});
it('renders single badge for discussion object', () => {
@@ -95,6 +97,8 @@ describe('Diffs image diff overlay component', () => {
y: 0,
width: 100,
height: 200,
+ xPercent: 0,
+ yPercent: 0,
});
});
@@ -120,11 +124,13 @@ describe('Diffs image diff overlay component', () => {
describe('comment form', () => {
const getCommentIndicator = () => wrapper.find('.comment-indicator');
beforeEach(() => {
- createComponent({}, store => {
+ createComponent({ canComment: true }, store => {
store.state.diffs.commentForms.push({
fileHash: 'ABC',
x: 20,
y: 10,
+ xPercent: 10,
+ yPercent: 10,
});
});
});
@@ -134,7 +140,7 @@ describe('Diffs image diff overlay component', () => {
});
it('sets comment form badge position', () => {
- expect(getCommentIndicator().attributes('style')).toBe('left: 20px; top: 10px;');
+ expect(getCommentIndicator().attributes('style')).toBe('left: 10%; top: 10%;');
});
});
});
diff --git a/spec/frontend/diffs/components/settings_dropdown_spec.js b/spec/frontend/diffs/components/settings_dropdown_spec.js
index 72330d8efba..eb9f9b4db73 100644
--- a/spec/frontend/diffs/components/settings_dropdown_spec.js
+++ b/spec/frontend/diffs/components/settings_dropdown_spec.js
@@ -2,12 +2,18 @@ import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import diffModule from '~/diffs/store/modules';
import SettingsDropdown from '~/diffs/components/settings_dropdown.vue';
-import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
+import {
+ EVT_VIEW_FILE_BY_FILE,
+ PARALLEL_DIFF_VIEW_TYPE,
+ INLINE_DIFF_VIEW_TYPE,
+} from '~/diffs/constants';
+import eventHub from '~/diffs/event_hub';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Diff settings dropdown component', () => {
+ let wrapper;
let vm;
let actions;
@@ -25,10 +31,15 @@ describe('Diff settings dropdown component', () => {
extendStore(store);
- vm = mount(SettingsDropdown, {
+ wrapper = mount(SettingsDropdown, {
localVue,
store,
});
+ vm = wrapper.vm;
+ }
+
+ function getFileByFileCheckbox(vueWrapper) {
+ return vueWrapper.find('[data-testid="file-by-file"]');
}
beforeEach(() => {
@@ -41,14 +52,14 @@ describe('Diff settings dropdown component', () => {
});
afterEach(() => {
- vm.destroy();
+ wrapper.destroy();
});
describe('tree view buttons', () => {
it('list view button dispatches setRenderTreeList with false', () => {
createComponent();
- vm.find('.js-list-view').trigger('click');
+ wrapper.find('.js-list-view').trigger('click');
expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), false);
});
@@ -56,7 +67,7 @@ describe('Diff settings dropdown component', () => {
it('tree view button dispatches setRenderTreeList with true', () => {
createComponent();
- vm.find('.js-tree-view').trigger('click');
+ wrapper.find('.js-tree-view').trigger('click');
expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), true);
});
@@ -68,8 +79,8 @@ describe('Diff settings dropdown component', () => {
});
});
- expect(vm.find('.js-list-view').classes('selected')).toBe(true);
- expect(vm.find('.js-tree-view').classes('selected')).toBe(false);
+ expect(wrapper.find('.js-list-view').classes('selected')).toBe(true);
+ expect(wrapper.find('.js-tree-view').classes('selected')).toBe(false);
});
it('sets tree button as selected when renderTreeList is true', () => {
@@ -79,8 +90,8 @@ describe('Diff settings dropdown component', () => {
});
});
- expect(vm.find('.js-list-view').classes('selected')).toBe(false);
- expect(vm.find('.js-tree-view').classes('selected')).toBe(true);
+ expect(wrapper.find('.js-list-view').classes('selected')).toBe(false);
+ expect(wrapper.find('.js-tree-view').classes('selected')).toBe(true);
});
});
@@ -92,8 +103,8 @@ describe('Diff settings dropdown component', () => {
});
});
- expect(vm.find('.js-inline-diff-button').classes('selected')).toBe(true);
- expect(vm.find('.js-parallel-diff-button').classes('selected')).toBe(false);
+ expect(wrapper.find('.js-inline-diff-button').classes('selected')).toBe(true);
+ expect(wrapper.find('.js-parallel-diff-button').classes('selected')).toBe(false);
});
it('sets parallel button as selected', () => {
@@ -103,14 +114,14 @@ describe('Diff settings dropdown component', () => {
});
});
- expect(vm.find('.js-inline-diff-button').classes('selected')).toBe(false);
- expect(vm.find('.js-parallel-diff-button').classes('selected')).toBe(true);
+ expect(wrapper.find('.js-inline-diff-button').classes('selected')).toBe(false);
+ expect(wrapper.find('.js-parallel-diff-button').classes('selected')).toBe(true);
});
it('calls setInlineDiffViewType when clicking inline button', () => {
createComponent();
- vm.find('.js-inline-diff-button').trigger('click');
+ wrapper.find('.js-inline-diff-button').trigger('click');
expect(actions.setInlineDiffViewType).toHaveBeenCalled();
});
@@ -118,7 +129,7 @@ describe('Diff settings dropdown component', () => {
it('calls setParallelDiffViewType when clicking parallel button', () => {
createComponent();
- vm.find('.js-parallel-diff-button').trigger('click');
+ wrapper.find('.js-parallel-diff-button').trigger('click');
expect(actions.setParallelDiffViewType).toHaveBeenCalled();
});
@@ -132,7 +143,7 @@ describe('Diff settings dropdown component', () => {
});
});
- expect(vm.find('#show-whitespace').element.checked).toBe(false);
+ expect(wrapper.find('#show-whitespace').element.checked).toBe(false);
});
it('sets as checked when showWhitespace is true', () => {
@@ -142,13 +153,13 @@ describe('Diff settings dropdown component', () => {
});
});
- expect(vm.find('#show-whitespace').element.checked).toBe(true);
+ expect(wrapper.find('#show-whitespace').element.checked).toBe(true);
});
it('calls setShowWhitespace on change', () => {
createComponent();
- const checkbox = vm.find('#show-whitespace');
+ const checkbox = wrapper.find('#show-whitespace');
checkbox.element.checked = true;
checkbox.trigger('change');
@@ -159,4 +170,52 @@ describe('Diff settings dropdown component', () => {
});
});
});
+
+ describe('file-by-file toggle', () => {
+ beforeEach(() => {
+ jest.spyOn(eventHub, '$emit');
+ });
+
+ it.each`
+ fileByFile | checked
+ ${true} | ${true}
+ ${false} | ${false}
+ `(
+ 'sets the checkbox to { checked: $checked } if the fileByFile setting is $fileByFile',
+ async ({ fileByFile, checked }) => {
+ createComponent(store => {
+ Object.assign(store.state.diffs, {
+ viewDiffsFileByFile: fileByFile,
+ });
+ });
+
+ await vm.$nextTick();
+
+ expect(getFileByFileCheckbox(wrapper).element.checked).toBe(checked);
+ },
+ );
+
+ it.each`
+ start | emit
+ ${true} | ${false}
+ ${false} | ${true}
+ `(
+ 'when the file by file setting starts as $start, toggling the checkbox should emit an event set to $emit',
+ async ({ start, emit }) => {
+ createComponent(store => {
+ Object.assign(store.state.diffs, {
+ viewDiffsFileByFile: start,
+ });
+ });
+
+ await vm.$nextTick();
+
+ getFileByFileCheckbox(wrapper).trigger('click');
+
+ await vm.$nextTick();
+
+ expect(eventHub.$emit).toHaveBeenCalledWith(EVT_VIEW_FILE_BY_FILE, { setting: emit });
+ },
+ );
+ });
});
diff --git a/spec/frontend/diffs/diff_file_spec.js b/spec/frontend/diffs/diff_file_spec.js
deleted file mode 100644
index 5d74760ef66..00000000000
--- a/spec/frontend/diffs/diff_file_spec.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { prepareRawDiffFile } from '~/diffs/diff_file';
-
-const DIFF_FILES = [
- {
- file_hash: 'ABC', // This file is just a normal file
- },
- {
- file_hash: 'DEF', // This file replaces a symlink
- a_mode: '0',
- b_mode: '0755',
- },
- {
- file_hash: 'DEF', // This symlink is replaced by a file
- a_mode: '120000',
- b_mode: '0',
- },
- {
- file_hash: 'GHI', // This symlink replaces a file
- a_mode: '0',
- b_mode: '120000',
- },
- {
- file_hash: 'GHI', // This file is replaced by a symlink
- a_mode: '0755',
- b_mode: '0',
- },
-];
-
-function makeBrokenSymlinkObject(replaced, wasSymbolic, isSymbolic, wasReal, isReal) {
- return {
- replaced,
- wasSymbolic,
- isSymbolic,
- wasReal,
- isReal,
- };
-}
-
-describe('diff_file utilities', () => {
- describe('prepareRawDiffFile', () => {
- it.each`
- fileIndex | description | brokenSymlink
- ${0} | ${'a file that is not symlink-adjacent'} | ${false}
- ${1} | ${'a file that replaces a symlink'} | ${makeBrokenSymlinkObject(false, false, false, false, true)}
- ${2} | ${'a symlink that is replaced by a file'} | ${makeBrokenSymlinkObject(true, true, false, false, false)}
- ${3} | ${'a symlink that replaces a file'} | ${makeBrokenSymlinkObject(false, false, true, false, false)}
- ${4} | ${'a file that is replaced by a symlink'} | ${makeBrokenSymlinkObject(true, false, false, true, false)}
- `(
- 'properly marks $description with the correct .brokenSymlink value',
- ({ fileIndex, brokenSymlink }) => {
- const preppedRaw = prepareRawDiffFile({
- file: DIFF_FILES[fileIndex],
- allFiles: DIFF_FILES,
- });
-
- expect(preppedRaw.brokenSymlink).toStrictEqual(brokenSymlink);
- },
- );
- });
-});
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 0af5ddd9764..fef7676e795 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -32,7 +32,7 @@ import {
setHighlightedRow,
toggleTreeOpen,
scrollToFile,
- toggleShowTreeList,
+ setShowTreeList,
renderFileForDiscussionId,
setRenderTreeList,
setShowWhitespace,
@@ -48,6 +48,7 @@ import {
moveToNeighboringCommit,
setCurrentDiffFileIdFromNote,
navigateToDiffFileIndex,
+ setFileByFile,
} from '~/diffs/store/actions';
import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
@@ -159,10 +160,10 @@ describe('DiffsStoreActions', () => {
.onGet(
mergeUrlParams(
{
- per_page: DIFFS_PER_PAGE,
w: '1',
view: 'inline',
page: 1,
+ per_page: DIFFS_PER_PAGE,
},
endpointBatch,
),
@@ -171,10 +172,10 @@ describe('DiffsStoreActions', () => {
.onGet(
mergeUrlParams(
{
- per_page: DIFFS_PER_PAGE,
w: '1',
view: 'inline',
page: 2,
+ per_page: DIFFS_PER_PAGE,
},
endpointBatch,
),
@@ -248,7 +249,7 @@ describe('DiffsStoreActions', () => {
{ type: types.SET_LOADING, payload: true },
{ type: types.SET_LOADING, payload: false },
{ type: types.SET_MERGE_REQUEST_DIFFS, payload: diffMetadata.merge_request_diffs },
- { type: types.SET_DIFF_DATA, payload: noFilesData },
+ { type: types.SET_DIFF_METADATA, payload: noFilesData },
],
[],
() => {
@@ -901,15 +902,22 @@ describe('DiffsStoreActions', () => {
});
});
- describe('toggleShowTreeList', () => {
+ describe('setShowTreeList', () => {
it('commits toggle', done => {
- testAction(toggleShowTreeList, null, {}, [{ type: types.TOGGLE_SHOW_TREE_LIST }], [], done);
+ testAction(
+ setShowTreeList,
+ { showTreeList: true },
+ {},
+ [{ type: types.SET_SHOW_TREE_LIST, payload: true }],
+ [],
+ done,
+ );
});
it('updates localStorage', () => {
jest.spyOn(localStorage, 'setItem').mockImplementation(() => {});
- toggleShowTreeList({ commit() {}, state: { showTreeList: true } });
+ setShowTreeList({ commit() {} }, { showTreeList: true });
expect(localStorage.setItem).toHaveBeenCalledWith('mr_tree_show', true);
});
@@ -917,7 +925,7 @@ describe('DiffsStoreActions', () => {
it('does not update localStorage', () => {
jest.spyOn(localStorage, 'setItem').mockImplementation(() => {});
- toggleShowTreeList({ commit() {}, state: { showTreeList: true } }, false);
+ setShowTreeList({ commit() {} }, { showTreeList: true, saving: false });
expect(localStorage.setItem).not.toHaveBeenCalled();
});
@@ -1236,10 +1244,6 @@ describe('DiffsStoreActions', () => {
{ diffViewType: 'inline' },
[
{
- type: 'SET_HIDDEN_VIEW_DIFF_FILE_LINES',
- payload: { filePath: 'path', lines: ['test'] },
- },
- {
type: 'SET_CURRENT_VIEW_DIFF_FILE_LINES',
payload: { filePath: 'path', lines: ['test'] },
},
@@ -1259,10 +1263,6 @@ describe('DiffsStoreActions', () => {
{ diffViewType: 'inline' },
[
{
- type: 'SET_HIDDEN_VIEW_DIFF_FILE_LINES',
- payload: { filePath: 'path', lines },
- },
- {
type: 'SET_CURRENT_VIEW_DIFF_FILE_LINES',
payload: { filePath: 'path', lines: lines.slice(0, 200) },
},
@@ -1456,4 +1456,20 @@ describe('DiffsStoreActions', () => {
);
});
});
+
+ describe('setFileByFile', () => {
+ it.each`
+ value
+ ${true}
+ ${false}
+ `('commits SET_FILE_BY_FILE with the new value $value', ({ value }) => {
+ return testAction(
+ setFileByFile,
+ { fileByFile: value },
+ { viewDiffsFileByFile: null },
+ [{ type: types.SET_FILE_BY_FILE, payload: value }],
+ [],
+ );
+ });
+ });
});
diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js
index c0645faf89e..13e7cad835d 100644
--- a/spec/frontend/diffs/store/mutations_spec.js
+++ b/spec/frontend/diffs/store/mutations_spec.js
@@ -1,7 +1,7 @@
import createState from '~/diffs/store/modules/diff_state';
import mutations from '~/diffs/store/mutations';
import * as types from '~/diffs/store/mutation_types';
-import { INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
+import { INLINE_DIFF_VIEW_TYPE, INLINE_DIFF_LINES_KEY } from '~/diffs/constants';
import diffFileMockData from '../mock_data/diff_file';
import * as utils from '~/diffs/store/utils';
@@ -67,28 +67,28 @@ describe('DiffsStoreMutations', () => {
});
});
- describe('SET_DIFF_DATA', () => {
- it('should not modify the existing state', () => {
+ describe('SET_DIFF_METADATA', () => {
+ it('should overwrite state with the camelCased data that is passed in', () => {
const state = {
- diffFiles: [
- {
- content_sha: diffFileMockData.content_sha,
- file_hash: diffFileMockData.file_hash,
- highlighted_diff_lines: [],
- },
- ],
+ diffFiles: [],
};
const diffMock = {
diff_files: [diffFileMockData],
};
+ const metaMock = {
+ other_key: 'value',
+ };
- mutations[types.SET_DIFF_DATA](state, diffMock);
+ mutations[types.SET_DIFF_METADATA](state, diffMock);
+ expect(state.diffFiles[0]).toEqual(diffFileMockData);
- expect(state.diffFiles[0].parallel_diff_lines).toBeUndefined();
+ mutations[types.SET_DIFF_METADATA](state, metaMock);
+ expect(state.diffFiles[0]).toEqual(diffFileMockData);
+ expect(state.otherKey).toEqual('value');
});
});
- describe('SET_DIFFSET_DIFF_DATA_BATCH_DATA', () => {
+ describe('SET_DIFF_DATA_BATCH_DATA', () => {
it('should set diff data batch type properly', () => {
const state = { diffFiles: [] };
const diffMock = {
@@ -97,9 +97,6 @@ describe('DiffsStoreMutations', () => {
mutations[types.SET_DIFF_DATA_BATCH](state, diffMock);
- const firstLine = state.diffFiles[0].parallel_diff_lines[0];
-
- expect(firstLine.right.text).toBeUndefined();
expect(state.diffFiles[0].renderIt).toEqual(true);
expect(state.diffFiles[0].collapsed).toEqual(false);
});
@@ -142,8 +139,7 @@ describe('DiffsStoreMutations', () => {
};
const diffFile = {
file_hash: options.fileHash,
- highlighted_diff_lines: [],
- parallel_diff_lines: [],
+ [INLINE_DIFF_LINES_KEY]: [],
};
const state = { diffFiles: [diffFile], diffViewType: 'viewType' };
const lines = [{ old_line: 1, new_line: 1 }];
@@ -171,9 +167,7 @@ describe('DiffsStoreMutations', () => {
);
expect(utils.addContextLines).toHaveBeenCalledWith({
- inlineLines: diffFile.highlighted_diff_lines,
- parallelLines: diffFile.parallel_diff_lines,
- diffViewType: 'viewType',
+ inlineLines: diffFile[INLINE_DIFF_LINES_KEY],
contextLines: options.contextLines,
bottom: options.params.bottom,
lineNumbers: options.lineNumbers,
@@ -225,19 +219,7 @@ describe('DiffsStoreMutations', () => {
diffFiles: [
{
file_hash: 'ABC',
- parallel_diff_lines: [
- {
- left: {
- line_code: 'ABC_1',
- discussions: [],
- },
- right: {
- line_code: 'ABC_2',
- discussions: [],
- },
- },
- ],
- highlighted_diff_lines: [
+ [INLINE_DIFF_LINES_KEY]: [
{
line_code: 'ABC_1',
discussions: [],
@@ -267,12 +249,8 @@ describe('DiffsStoreMutations', () => {
diffPositionByLineCode,
});
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1);
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1);
- expect(state.diffFiles[0].parallel_diff_lines[0].right.discussions).toEqual([]);
-
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1);
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions.length).toEqual(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions[0].id).toEqual(1);
});
it('should not duplicate discussions on line', () => {
@@ -291,19 +269,7 @@ describe('DiffsStoreMutations', () => {
diffFiles: [
{
file_hash: 'ABC',
- parallel_diff_lines: [
- {
- left: {
- line_code: 'ABC_1',
- discussions: [],
- },
- right: {
- line_code: 'ABC_2',
- discussions: [],
- },
- },
- ],
- highlighted_diff_lines: [
+ [INLINE_DIFF_LINES_KEY]: [
{
line_code: 'ABC_1',
discussions: [],
@@ -333,24 +299,16 @@ describe('DiffsStoreMutations', () => {
diffPositionByLineCode,
});
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1);
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1);
- expect(state.diffFiles[0].parallel_diff_lines[0].right.discussions).toEqual([]);
-
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1);
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions.length).toEqual(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions[0].id).toEqual(1);
mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, {
discussion,
diffPositionByLineCode,
});
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1);
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1);
- expect(state.diffFiles[0].parallel_diff_lines[0].right.discussions).toEqual([]);
-
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1);
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions.length).toEqual(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions[0].id).toEqual(1);
});
it('updates existing discussion', () => {
@@ -369,19 +327,7 @@ describe('DiffsStoreMutations', () => {
diffFiles: [
{
file_hash: 'ABC',
- parallel_diff_lines: [
- {
- left: {
- line_code: 'ABC_1',
- discussions: [],
- },
- right: {
- line_code: 'ABC_2',
- discussions: [],
- },
- },
- ],
- highlighted_diff_lines: [
+ [INLINE_DIFF_LINES_KEY]: [
{
line_code: 'ABC_1',
discussions: [],
@@ -411,12 +357,8 @@ describe('DiffsStoreMutations', () => {
diffPositionByLineCode,
});
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1);
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1);
- expect(state.diffFiles[0].parallel_diff_lines[0].right.discussions).toEqual([]);
-
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1);
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions.length).toEqual(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions[0].id).toEqual(1);
mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, {
discussion: {
@@ -427,11 +369,8 @@ describe('DiffsStoreMutations', () => {
diffPositionByLineCode,
});
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].notes.length).toBe(1);
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].notes.length).toBe(1);
-
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].resolved).toBe(true);
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].resolved).toBe(true);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions[0].notes.length).toBe(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions[0].resolved).toBe(true);
});
it('should not duplicate inline diff discussions', () => {
@@ -450,7 +389,7 @@ describe('DiffsStoreMutations', () => {
diffFiles: [
{
file_hash: 'ABC',
- highlighted_diff_lines: [
+ [INLINE_DIFF_LINES_KEY]: [
{
line_code: 'ABC_1',
discussions: [
@@ -472,7 +411,6 @@ describe('DiffsStoreMutations', () => {
discussions: [],
},
],
- parallel_diff_lines: [],
},
],
};
@@ -497,7 +435,7 @@ describe('DiffsStoreMutations', () => {
diffPositionByLineCode,
});
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toBe(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions.length).toBe(1);
});
it('should add legacy discussions to the given line', () => {
@@ -517,19 +455,7 @@ describe('DiffsStoreMutations', () => {
diffFiles: [
{
file_hash: 'ABC',
- parallel_diff_lines: [
- {
- left: {
- line_code: 'ABC_1',
- discussions: [],
- },
- right: {
- line_code: 'ABC_1',
- discussions: [],
- },
- },
- ],
- highlighted_diff_lines: [
+ [INLINE_DIFF_LINES_KEY]: [
{
line_code: 'ABC_1',
discussions: [],
@@ -557,11 +483,8 @@ describe('DiffsStoreMutations', () => {
diffPositionByLineCode,
});
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1);
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1);
-
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1);
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions.length).toEqual(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions[0].id).toEqual(1);
});
it('should add discussions by line_codes and positions attributes', () => {
@@ -580,19 +503,7 @@ describe('DiffsStoreMutations', () => {
diffFiles: [
{
file_hash: 'ABC',
- parallel_diff_lines: [
- {
- left: {
- line_code: 'ABC_1',
- discussions: [],
- },
- right: {
- line_code: 'ABC_1',
- discussions: [],
- },
- },
- ],
- highlighted_diff_lines: [
+ [INLINE_DIFF_LINES_KEY]: [
{
line_code: 'ABC_1',
discussions: [],
@@ -624,11 +535,8 @@ describe('DiffsStoreMutations', () => {
diffPositionByLineCode,
});
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions).toHaveLength(1);
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toBe(1);
-
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions).toHaveLength(1);
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toBe(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions).toHaveLength(1);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions[0].id).toBe(1);
});
it('should add discussion to file', () => {
@@ -638,8 +546,7 @@ describe('DiffsStoreMutations', () => {
{
file_hash: 'ABC',
discussions: [],
- parallel_diff_lines: [],
- highlighted_diff_lines: [],
+ [INLINE_DIFF_LINES_KEY]: [],
},
],
};
@@ -668,30 +575,7 @@ describe('DiffsStoreMutations', () => {
diffFiles: [
{
file_hash: 'ABC',
- parallel_diff_lines: [
- {
- left: {
- line_code: 'ABC_1',
- discussions: [
- {
- id: 1,
- line_code: 'ABC_1',
- notes: [],
- },
- {
- id: 2,
- line_code: 'ABC_1',
- notes: [],
- },
- ],
- },
- right: {
- line_code: 'ABC_1',
- discussions: [],
- },
- },
- ],
- highlighted_diff_lines: [
+ [INLINE_DIFF_LINES_KEY]: [
{
line_code: 'ABC_1',
discussions: [
@@ -717,8 +601,7 @@ describe('DiffsStoreMutations', () => {
lineCode: 'ABC_1',
});
- expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(0);
- expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(0);
+ expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY][0].discussions.length).toEqual(0);
});
});
@@ -738,15 +621,11 @@ describe('DiffsStoreMutations', () => {
});
});
- describe('TOGGLE_SHOW_TREE_LIST', () => {
- it('toggles showTreeList', () => {
+ describe('SET_SHOW_TREE_LIST', () => {
+ it('sets showTreeList', () => {
const state = createState();
- mutations[types.TOGGLE_SHOW_TREE_LIST](state);
-
- expect(state.showTreeList).toBe(false, 'Failed to toggle showTreeList to false');
-
- mutations[types.TOGGLE_SHOW_TREE_LIST](state);
+ mutations[types.SET_SHOW_TREE_LIST](state, true);
expect(state.showTreeList).toBe(true, 'Failed to toggle showTreeList to true');
});
@@ -776,11 +655,7 @@ describe('DiffsStoreMutations', () => {
it('sets hasForm on lines', () => {
const file = {
file_hash: 'hash',
- parallel_diff_lines: [
- { left: { line_code: '123', hasForm: false }, right: {} },
- { left: {}, right: { line_code: '124', hasForm: false } },
- ],
- highlighted_diff_lines: [
+ [INLINE_DIFF_LINES_KEY]: [
{ line_code: '123', hasForm: false },
{ line_code: '124', hasForm: false },
],
@@ -795,11 +670,8 @@ describe('DiffsStoreMutations', () => {
fileHash: 'hash',
});
- expect(file.highlighted_diff_lines[0].hasForm).toBe(true);
- expect(file.highlighted_diff_lines[1].hasForm).toBe(false);
-
- expect(file.parallel_diff_lines[0].left.hasForm).toBe(true);
- expect(file.parallel_diff_lines[1].right.hasForm).toBe(false);
+ expect(file[INLINE_DIFF_LINES_KEY][0].hasForm).toBe(true);
+ expect(file[INLINE_DIFF_LINES_KEY][1].hasForm).toBe(false);
});
});
@@ -885,8 +757,7 @@ describe('DiffsStoreMutations', () => {
file_path: 'test',
isLoadingFullFile: true,
isShowingFullFile: false,
- highlighted_diff_lines: [],
- parallel_diff_lines: [],
+ [INLINE_DIFF_LINES_KEY]: [],
},
],
};
@@ -903,8 +774,7 @@ describe('DiffsStoreMutations', () => {
file_path: 'test',
isLoadingFullFile: true,
isShowingFullFile: false,
- highlighted_diff_lines: [],
- parallel_diff_lines: [],
+ [INLINE_DIFF_LINES_KEY]: [],
},
],
};
@@ -927,80 +797,42 @@ describe('DiffsStoreMutations', () => {
});
});
- describe('SET_HIDDEN_VIEW_DIFF_FILE_LINES', () => {
- [
- { current: 'highlighted', hidden: 'parallel', diffViewType: 'inline' },
- { current: 'parallel', hidden: 'highlighted', diffViewType: 'parallel' },
- ].forEach(({ current, hidden, diffViewType }) => {
- it(`sets the ${hidden} lines when diff view is ${diffViewType}`, () => {
- const file = { file_path: 'test', parallel_diff_lines: [], highlighted_diff_lines: [] };
- const state = {
- diffFiles: [file],
- diffViewType,
- };
-
- mutations[types.SET_HIDDEN_VIEW_DIFF_FILE_LINES](state, {
- filePath: 'test',
- lines: ['test'],
- });
-
- expect(file[`${current}_diff_lines`]).toEqual([]);
- expect(file[`${hidden}_diff_lines`]).toEqual(['test']);
- });
- });
- });
-
describe('SET_CURRENT_VIEW_DIFF_FILE_LINES', () => {
- [
- { current: 'highlighted', hidden: 'parallel', diffViewType: 'inline' },
- { current: 'parallel', hidden: 'highlighted', diffViewType: 'parallel' },
- ].forEach(({ current, hidden, diffViewType }) => {
- it(`sets the ${current} lines when diff view is ${diffViewType}`, () => {
- const file = { file_path: 'test', parallel_diff_lines: [], highlighted_diff_lines: [] };
- const state = {
- diffFiles: [file],
- diffViewType,
- };
-
- mutations[types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, {
- filePath: 'test',
- lines: ['test'],
- });
-
- expect(file[`${current}_diff_lines`]).toEqual(['test']);
- expect(file[`${hidden}_diff_lines`]).toEqual([]);
+ it(`sets the highlighted lines`, () => {
+ const file = { file_path: 'test', [INLINE_DIFF_LINES_KEY]: [] };
+ const state = {
+ diffFiles: [file],
+ };
+
+ mutations[types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, {
+ filePath: 'test',
+ lines: ['test'],
});
+
+ expect(file[INLINE_DIFF_LINES_KEY]).toEqual(['test']);
});
});
describe('ADD_CURRENT_VIEW_DIFF_FILE_LINES', () => {
- [
- { current: 'highlighted', hidden: 'parallel', diffViewType: 'inline' },
- { current: 'parallel', hidden: 'highlighted', diffViewType: 'parallel' },
- ].forEach(({ current, hidden, diffViewType }) => {
- it(`pushes to ${current} lines when diff view is ${diffViewType}`, () => {
- const file = { file_path: 'test', parallel_diff_lines: [], highlighted_diff_lines: [] };
- const state = {
- diffFiles: [file],
- diffViewType,
- };
-
- mutations[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, {
- filePath: 'test',
- line: 'test',
- });
-
- expect(file[`${current}_diff_lines`]).toEqual(['test']);
- expect(file[`${hidden}_diff_lines`]).toEqual([]);
-
- mutations[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, {
- filePath: 'test',
- line: 'test2',
- });
-
- expect(file[`${current}_diff_lines`]).toEqual(['test', 'test2']);
- expect(file[`${hidden}_diff_lines`]).toEqual([]);
+ it('pushes to inline lines', () => {
+ const file = { file_path: 'test', [INLINE_DIFF_LINES_KEY]: [] };
+ const state = {
+ diffFiles: [file],
+ };
+
+ mutations[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, {
+ filePath: 'test',
+ line: 'test',
+ });
+
+ expect(file[INLINE_DIFF_LINES_KEY]).toEqual(['test']);
+
+ mutations[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, {
+ filePath: 'test',
+ line: 'test2',
});
+
+ expect(file[INLINE_DIFF_LINES_KEY]).toEqual(['test', 'test2']);
});
});
@@ -1060,4 +892,18 @@ describe('DiffsStoreMutations', () => {
expect(state.showSuggestPopover).toBe(false);
});
});
+
+ describe('SET_FILE_BY_FILE', () => {
+ it.each`
+ value | opposite
+ ${true} | ${false}
+ ${false} | ${true}
+ `('sets viewDiffsFileByFile to $value', ({ value, opposite }) => {
+ const state = { viewDiffsFileByFile: opposite };
+
+ mutations[types.SET_FILE_BY_FILE](state, value);
+
+ expect(state.viewDiffsFileByFile).toBe(value);
+ });
+ });
});
diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js
index 866be0abd22..7ee97224707 100644
--- a/spec/frontend/diffs/store/utils_spec.js
+++ b/spec/frontend/diffs/store/utils_spec.js
@@ -10,7 +10,7 @@ import {
OLD_LINE_TYPE,
MATCH_LINE_TYPE,
INLINE_DIFF_VIEW_TYPE,
- PARALLEL_DIFF_VIEW_TYPE,
+ INLINE_DIFF_LINES_KEY,
} from '~/diffs/constants';
import { MERGE_REQUEST_NOTEABLE_TYPE } from '~/notes/constants';
import diffFileMockData from '../mock_data/diff_file';
@@ -20,14 +20,6 @@ import { noteableDataMock } from '../../notes/mock_data';
const getDiffFileMock = () => JSON.parse(JSON.stringify(diffFileMockData));
const getDiffMetadataMock = () => JSON.parse(JSON.stringify(diffMetadata));
-function extractLinesFromFile(file) {
- const unpackedParallel = file.parallel_diff_lines
- .flatMap(({ left, right }) => [left, right])
- .filter(Boolean);
-
- return [...file.highlighted_diff_lines, ...unpackedParallel];
-}
-
describe('DiffsStoreUtils', () => {
describe('findDiffFile', () => {
const files = [{ file_hash: 1, name: 'one' }];
@@ -45,7 +37,7 @@ describe('DiffsStoreUtils', () => {
});
});
- describe('findIndexInInlineLines and findIndexInParallelLines', () => {
+ describe('findIndexInInlineLines', () => {
const expectSet = (method, lines, invalidLines) => {
expect(method(lines, { oldLineNumber: 3, newLineNumber: 5 })).toEqual(4);
expect(method(invalidLines || lines, { oldLineNumber: 32, newLineNumber: 53 })).toEqual(-1);
@@ -53,44 +45,26 @@ describe('DiffsStoreUtils', () => {
describe('findIndexInInlineLines', () => {
it('should return correct index for given line numbers', () => {
- expectSet(utils.findIndexInInlineLines, getDiffFileMock().highlighted_diff_lines);
- });
- });
-
- describe('findIndexInParallelLines', () => {
- it('should return correct index for given line numbers', () => {
- expectSet(utils.findIndexInParallelLines, getDiffFileMock().parallel_diff_lines, []);
+ expectSet(utils.findIndexInInlineLines, getDiffFileMock()[INLINE_DIFF_LINES_KEY]);
});
});
});
describe('getPreviousLineIndex', () => {
- [
- { diffViewType: INLINE_DIFF_VIEW_TYPE, file: { parallel_diff_lines: [] } },
- { diffViewType: PARALLEL_DIFF_VIEW_TYPE, file: { highlighted_diff_lines: [] } },
- ].forEach(({ diffViewType, file }) => {
- describe(`with diffViewType (${diffViewType}) in split diffs`, () => {
- let diffFile;
-
- beforeEach(() => {
- diffFile = { ...clone(diffFileMockData), ...file };
- });
+ describe(`with diffViewType (inline) in split diffs`, () => {
+ let diffFile;
- it('should return the correct previous line number', () => {
- const emptyLines =
- diffViewType === INLINE_DIFF_VIEW_TYPE
- ? diffFile.parallel_diff_lines
- : diffFile.highlighted_diff_lines;
-
- // This expectation asserts that we cannot possibly be using the opposite view type lines in the next expectation
- expect(emptyLines.length).toBe(0);
- expect(
- utils.getPreviousLineIndex(diffViewType, diffFile, {
- oldLineNumber: 3,
- newLineNumber: 5,
- }),
- ).toBe(4);
- });
+ beforeEach(() => {
+ diffFile = { ...clone(diffFileMockData) };
+ });
+
+ it('should return the correct previous line number', () => {
+ expect(
+ utils.getPreviousLineIndex(INLINE_DIFF_VIEW_TYPE, diffFile, {
+ oldLineNumber: 3,
+ newLineNumber: 5,
+ }),
+ ).toBe(4);
});
});
});
@@ -100,82 +74,50 @@ describe('DiffsStoreUtils', () => {
const diffFile = getDiffFileMock();
const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
const inlineIndex = utils.findIndexInInlineLines(
- diffFile.highlighted_diff_lines,
+ diffFile[INLINE_DIFF_LINES_KEY],
lineNumbers,
);
- const parallelIndex = utils.findIndexInParallelLines(
- diffFile.parallel_diff_lines,
- lineNumbers,
- );
- const atInlineIndex = diffFile.highlighted_diff_lines[inlineIndex];
- const atParallelIndex = diffFile.parallel_diff_lines[parallelIndex];
+ const atInlineIndex = diffFile[INLINE_DIFF_LINES_KEY][inlineIndex];
utils.removeMatchLine(diffFile, lineNumbers, false);
- expect(diffFile.highlighted_diff_lines[inlineIndex]).not.toEqual(atInlineIndex);
- expect(diffFile.parallel_diff_lines[parallelIndex]).not.toEqual(atParallelIndex);
+ expect(diffFile[INLINE_DIFF_LINES_KEY][inlineIndex]).not.toEqual(atInlineIndex);
utils.removeMatchLine(diffFile, lineNumbers, true);
- expect(diffFile.highlighted_diff_lines[inlineIndex + 1]).not.toEqual(atInlineIndex);
- expect(diffFile.parallel_diff_lines[parallelIndex + 1]).not.toEqual(atParallelIndex);
+ expect(diffFile[INLINE_DIFF_LINES_KEY][inlineIndex + 1]).not.toEqual(atInlineIndex);
});
});
describe('addContextLines', () => {
- [INLINE_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE].forEach(diffViewType => {
- it(`should add context lines for ${diffViewType}`, () => {
- const diffFile = getDiffFileMock();
- const inlineLines = diffFile.highlighted_diff_lines;
- const parallelLines = diffFile.parallel_diff_lines;
- const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
- const contextLines = [{ lineNumber: 42, line_code: '123' }];
- const options = { inlineLines, parallelLines, contextLines, lineNumbers, diffViewType };
- const inlineIndex = utils.findIndexInInlineLines(inlineLines, lineNumbers);
- const parallelIndex = utils.findIndexInParallelLines(parallelLines, lineNumbers);
- const normalizedParallelLine = {
- left: options.contextLines[0],
- right: options.contextLines[0],
- line_code: '123',
- };
+ it(`should add context lines`, () => {
+ const diffFile = getDiffFileMock();
+ const inlineLines = diffFile[INLINE_DIFF_LINES_KEY];
+ const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
+ const contextLines = [{ lineNumber: 42, line_code: '123' }];
+ const options = { inlineLines, contextLines, lineNumbers };
+ const inlineIndex = utils.findIndexInInlineLines(inlineLines, lineNumbers);
- utils.addContextLines(options);
+ utils.addContextLines(options);
- if (diffViewType === INLINE_DIFF_VIEW_TYPE) {
- expect(inlineLines[inlineIndex]).toEqual(contextLines[0]);
- } else {
- expect(parallelLines[parallelIndex]).toEqual(normalizedParallelLine);
- }
- });
+ expect(inlineLines[inlineIndex]).toEqual(contextLines[0]);
+ });
- it(`should add context lines properly with bottom parameter for ${diffViewType}`, () => {
- const diffFile = getDiffFileMock();
- const inlineLines = diffFile.highlighted_diff_lines;
- const parallelLines = diffFile.parallel_diff_lines;
- const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
- const contextLines = [{ lineNumber: 42, line_code: '123' }];
- const options = {
- inlineLines,
- parallelLines,
- contextLines,
- lineNumbers,
- bottom: true,
- diffViewType,
- };
- const normalizedParallelLine = {
- left: options.contextLines[0],
- right: options.contextLines[0],
- line_code: '123',
- };
+ it(`should add context lines properly with bottom parameter`, () => {
+ const diffFile = getDiffFileMock();
+ const inlineLines = diffFile[INLINE_DIFF_LINES_KEY];
+ const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
+ const contextLines = [{ lineNumber: 42, line_code: '123' }];
+ const options = {
+ inlineLines,
+ contextLines,
+ lineNumbers,
+ bottom: true,
+ };
- utils.addContextLines(options);
+ utils.addContextLines(options);
- if (diffViewType === INLINE_DIFF_VIEW_TYPE) {
- expect(inlineLines[inlineLines.length - 1]).toEqual(contextLines[0]);
- } else {
- expect(parallelLines[parallelLines.length - 1]).toEqual(normalizedParallelLine);
- }
- });
+ expect(inlineLines[inlineLines.length - 1]).toEqual(contextLines[0]);
});
});
@@ -195,7 +137,6 @@ describe('DiffsStoreUtils', () => {
new_line: 3,
old_line: 1,
},
- diffViewType: PARALLEL_DIFF_VIEW_TYPE,
linePosition: LINE_POSITION_LEFT,
lineRange: { start_line_code: 'abc_1_1', end_line_code: 'abc_2_2' },
};
@@ -256,7 +197,6 @@ describe('DiffsStoreUtils', () => {
new_line: 3,
old_line: 1,
},
- diffViewType: PARALLEL_DIFF_VIEW_TYPE,
linePosition: LINE_POSITION_LEFT,
};
@@ -424,20 +364,6 @@ describe('DiffsStoreUtils', () => {
expect(preppedLine).toEqual(correctLine);
});
- it('returns a nested object with "left" and "right" lines + the line code for `parallel` lines', () => {
- preppedLine = utils.prepareLineForRenamedFile({
- diffViewType: PARALLEL_DIFF_VIEW_TYPE,
- line: sourceLine,
- index: lineIndex,
- diffFile,
- });
-
- expect(Object.keys(preppedLine)).toEqual(['left', 'right', 'line_code']);
- expect(preppedLine.left).toEqual(correctLine);
- expect(preppedLine.right).toEqual(correctLine);
- expect(preppedLine.line_code).toEqual(correctLine.line_code);
- });
-
it.each`
brokenSymlink
${false}
@@ -474,35 +400,26 @@ describe('DiffsStoreUtils', () => {
preparedDiff = { diff_files: [mock] };
splitInlineDiff = {
- diff_files: [{ ...mock, parallel_diff_lines: undefined }],
+ diff_files: [{ ...mock }],
};
splitParallelDiff = {
- diff_files: [{ ...mock, highlighted_diff_lines: undefined }],
+ diff_files: [{ ...mock, [INLINE_DIFF_LINES_KEY]: undefined }],
};
completedDiff = {
- diff_files: [{ ...mock, highlighted_diff_lines: undefined }],
+ diff_files: [{ ...mock, [INLINE_DIFF_LINES_KEY]: undefined }],
};
- preparedDiff.diff_files = utils.prepareDiffData(preparedDiff);
- splitInlineDiff.diff_files = utils.prepareDiffData(splitInlineDiff);
- splitParallelDiff.diff_files = utils.prepareDiffData(splitParallelDiff);
- completedDiff.diff_files = utils.prepareDiffData(completedDiff, [mock]);
+ preparedDiff.diff_files = utils.prepareDiffData({ diff: preparedDiff });
+ splitInlineDiff.diff_files = utils.prepareDiffData({ diff: splitInlineDiff });
+ splitParallelDiff.diff_files = utils.prepareDiffData({ diff: splitParallelDiff });
+ completedDiff.diff_files = utils.prepareDiffData({
+ diff: completedDiff,
+ priorFiles: [mock],
+ });
});
it('sets the renderIt and collapsed attribute on files', () => {
- const firstParallelDiffLine = preparedDiff.diff_files[0].parallel_diff_lines[2];
-
- expect(firstParallelDiffLine.left.discussions.length).toBe(0);
- expect(firstParallelDiffLine.left).not.toHaveAttr('text');
- expect(firstParallelDiffLine.right.discussions.length).toBe(0);
- expect(firstParallelDiffLine.right).not.toHaveAttr('text');
- const firstParallelChar = firstParallelDiffLine.right.rich_text.charAt(0);
-
- expect(firstParallelChar).not.toBe(' ');
- expect(firstParallelChar).not.toBe('+');
- expect(firstParallelChar).not.toBe('-');
-
- const checkLine = preparedDiff.diff_files[0].highlighted_diff_lines[0];
+ const checkLine = preparedDiff.diff_files[0][INLINE_DIFF_LINES_KEY][0];
expect(checkLine.discussions.length).toBe(0);
expect(checkLine).not.toHaveAttr('text');
@@ -516,29 +433,14 @@ describe('DiffsStoreUtils', () => {
expect(preparedDiff.diff_files[0].collapsed).toBeFalsy();
});
- it('adds line_code to all lines', () => {
- expect(
- preparedDiff.diff_files[0].parallel_diff_lines.filter(line => !line.line_code),
- ).toHaveLength(0);
- });
-
- it('uses right line code if left has none', () => {
- const firstLine = preparedDiff.diff_files[0].parallel_diff_lines[0];
-
- expect(firstLine.line_code).toEqual(firstLine.right.line_code);
- });
-
it('guarantees an empty array for both diff styles', () => {
- expect(splitInlineDiff.diff_files[0].parallel_diff_lines.length).toEqual(0);
- expect(splitInlineDiff.diff_files[0].highlighted_diff_lines.length).toBeGreaterThan(0);
- expect(splitParallelDiff.diff_files[0].parallel_diff_lines.length).toBeGreaterThan(0);
- expect(splitParallelDiff.diff_files[0].highlighted_diff_lines.length).toEqual(0);
+ expect(splitInlineDiff.diff_files[0][INLINE_DIFF_LINES_KEY].length).toBeGreaterThan(0);
+ expect(splitParallelDiff.diff_files[0][INLINE_DIFF_LINES_KEY].length).toEqual(0);
});
it('merges existing diff files with newly loaded diff files to ensure split diffs are eventually completed', () => {
expect(completedDiff.diff_files.length).toEqual(1);
- expect(completedDiff.diff_files[0].parallel_diff_lines.length).toBeGreaterThan(0);
- expect(completedDiff.diff_files[0].highlighted_diff_lines.length).toBeGreaterThan(0);
+ expect(completedDiff.diff_files[0][INLINE_DIFF_LINES_KEY].length).toBeGreaterThan(0);
});
it('leaves files in the existing state', () => {
@@ -548,20 +450,26 @@ describe('DiffsStoreUtils', () => {
content_sha: 'ABC',
file_hash: 'DEF',
};
- const updatedFilesList = utils.prepareDiffData({ diff_files: [fakeNewFile] }, priorFiles);
+ const updatedFilesList = utils.prepareDiffData({
+ diff: { diff_files: [fakeNewFile] },
+ priorFiles,
+ });
expect(updatedFilesList).toEqual([mock, fakeNewFile]);
});
it('completes an existing split diff without overwriting existing diffs', () => {
// The current state has a file that has only loaded inline lines
- const priorFiles = [{ ...mock, parallel_diff_lines: [] }];
+ const priorFiles = [{ ...mock }];
// The next (batch) load loads two files: the other half of that file, and a new file
const fakeBatch = [
- { ...mock, highlighted_diff_lines: undefined },
- { ...mock, highlighted_diff_lines: undefined, content_sha: 'ABC', file_hash: 'DEF' },
+ { ...mock, [INLINE_DIFF_LINES_KEY]: undefined },
+ { ...mock, [INLINE_DIFF_LINES_KEY]: undefined, content_sha: 'ABC', file_hash: 'DEF' },
];
- const updatedFilesList = utils.prepareDiffData({ diff_files: fakeBatch }, priorFiles);
+ const updatedFilesList = utils.prepareDiffData({
+ diff: { diff_files: fakeBatch },
+ priorFiles,
+ });
expect(updatedFilesList).toEqual([
mock,
@@ -584,7 +492,7 @@ describe('DiffsStoreUtils', () => {
...splitInlineDiff.diff_files,
...splitParallelDiff.diff_files,
...completedDiff.diff_files,
- ].flatMap(file => extractLinesFromFile(file));
+ ].flatMap(file => [...file[INLINE_DIFF_LINES_KEY]]);
lines.forEach(line => {
expect(line.commentsDisabled).toBe(false);
@@ -599,7 +507,7 @@ describe('DiffsStoreUtils', () => {
beforeEach(() => {
mock = getDiffMetadataMock();
- preparedDiffFiles = utils.prepareDiffData(mock);
+ preparedDiffFiles = utils.prepareDiffData({ diff: mock, meta: true });
});
it('sets the renderIt and collapsed attribute on files', () => {
@@ -608,15 +516,14 @@ describe('DiffsStoreUtils', () => {
});
it('guarantees an empty array of lines for both diff styles', () => {
- expect(preparedDiffFiles[0].parallel_diff_lines.length).toEqual(0);
- expect(preparedDiffFiles[0].highlighted_diff_lines.length).toEqual(0);
+ expect(preparedDiffFiles[0][INLINE_DIFF_LINES_KEY].length).toEqual(0);
});
it('leaves files in the existing state', () => {
const fileMock = getDiffFileMock();
const metaData = getDiffMetadataMock();
const priorFiles = [fileMock];
- const updatedFilesList = utils.prepareDiffData(metaData, priorFiles);
+ const updatedFilesList = utils.prepareDiffData({ diff: metaData, priorFiles, meta: true });
expect(updatedFilesList.length).toEqual(2);
expect(updatedFilesList[0]).toEqual(fileMock);
@@ -641,14 +548,13 @@ describe('DiffsStoreUtils', () => {
const fileMock = getDiffFileMock();
const metaMock = getDiffMetadataMock();
const priorFiles = [{ ...fileMock }];
- const updatedFilesList = utils.prepareDiffData(metaMock, priorFiles);
+ const updatedFilesList = utils.prepareDiffData({ diff: metaMock, priorFiles, meta: true });
expect(updatedFilesList).toEqual([
fileMock,
{
...metaMock.diff_files[0],
- highlighted_diff_lines: [],
- parallel_diff_lines: [],
+ [INLINE_DIFF_LINES_KEY]: [],
},
]);
});
@@ -1217,30 +1123,19 @@ describe('DiffsStoreUtils', () => {
it('converts inline diff lines to parallel diff lines', () => {
const file = getDiffFileMock();
- expect(utils.parallelizeDiffLines(file.highlighted_diff_lines)).toEqual(
+ expect(utils.parallelizeDiffLines(file[INLINE_DIFF_LINES_KEY])).toEqual(
file.parallel_diff_lines,
);
});
- /**
- * What's going on here?
- *
- * The inline version of parallelizeDiffLines simply keeps the difflines
- * in the same order they are received as opposed to shuffling them
- * to be "side by side".
- *
- * This keeps the underlying data structure the same which simplifies
- * the components, but keeps the changes grouped together as users
- * expect when viewing changes inline.
- */
- it('converts inline diff lines to inline diff lines with a parallel structure', () => {
+ it('converts inline diff lines', () => {
const file = getDiffFileMock();
const files = utils.parallelizeDiffLines(file.highlighted_diff_lines, true);
expect(files[5].left).toEqual(file.parallel_diff_lines[5].left);
expect(files[5].right).toBeNull();
- expect(files[6].left).toBeNull();
- expect(files[6].right).toEqual(file.parallel_diff_lines[5].right);
+ expect(files[6].left).toEqual(file.parallel_diff_lines[5].right);
+ expect(files[6].right).toBeNull();
});
});
});
diff --git a/spec/frontend/diffs/utils/diff_file_spec.js b/spec/frontend/diffs/utils/diff_file_spec.js
new file mode 100644
index 00000000000..2e6247b8c07
--- /dev/null
+++ b/spec/frontend/diffs/utils/diff_file_spec.js
@@ -0,0 +1,126 @@
+import { prepareRawDiffFile } from '~/diffs/utils/diff_file';
+
+function getDiffFiles() {
+ return [
+ {
+ blob: {
+ id: 'C0473471',
+ },
+ file_hash: 'ABC', // This file is just a normal file
+ file_identifier_hash: 'ABC1',
+ },
+ {
+ blob: {
+ id: 'C0473472',
+ },
+ file_hash: 'DEF', // This file replaces a symlink
+ file_identifier_hash: 'DEF1',
+ a_mode: '0',
+ b_mode: '0755',
+ },
+ {
+ blob: {
+ id: 'C0473473',
+ },
+ file_hash: 'DEF', // This symlink is replaced by a file
+ file_identifier_hash: 'DEF2',
+ a_mode: '120000',
+ b_mode: '0',
+ },
+ {
+ blob: {
+ id: 'C0473474',
+ },
+ file_hash: 'GHI', // This symlink replaces a file
+ file_identifier_hash: 'GHI1',
+ a_mode: '0',
+ b_mode: '120000',
+ },
+ {
+ blob: {
+ id: 'C0473475',
+ },
+ file_hash: 'GHI', // This file is replaced by a symlink
+ file_identifier_hash: 'GHI2',
+ a_mode: '0755',
+ b_mode: '0',
+ },
+ ];
+}
+function makeBrokenSymlinkObject(replaced, wasSymbolic, isSymbolic, wasReal, isReal) {
+ return {
+ replaced,
+ wasSymbolic,
+ isSymbolic,
+ wasReal,
+ isReal,
+ };
+}
+
+describe('diff_file utilities', () => {
+ describe('prepareRawDiffFile', () => {
+ let files;
+
+ beforeEach(() => {
+ files = getDiffFiles();
+ });
+
+ it.each`
+ fileIndex | description | brokenSymlink
+ ${0} | ${'a file that is not symlink-adjacent'} | ${false}
+ ${1} | ${'a file that replaces a symlink'} | ${makeBrokenSymlinkObject(false, false, false, false, true)}
+ ${2} | ${'a symlink that is replaced by a file'} | ${makeBrokenSymlinkObject(true, true, false, false, false)}
+ ${3} | ${'a symlink that replaces a file'} | ${makeBrokenSymlinkObject(false, false, true, false, false)}
+ ${4} | ${'a file that is replaced by a symlink'} | ${makeBrokenSymlinkObject(true, false, false, true, false)}
+ `(
+ 'properly marks $description with the correct .brokenSymlink value',
+ ({ fileIndex, brokenSymlink }) => {
+ const preppedRaw = prepareRawDiffFile({
+ file: files[fileIndex],
+ allFiles: files,
+ });
+
+ expect(preppedRaw.brokenSymlink).toStrictEqual(brokenSymlink);
+ },
+ );
+
+ it.each`
+ fileIndex | id
+ ${0} | ${'8dcd585e-a421-4dab-a04e-6f88c81b7b4c'}
+ ${1} | ${'3f178b78-392b-44a4-bd7d-5d6192208a97'}
+ ${2} | ${'3d9e1354-cddf-4a11-8234-f0413521b2e5'}
+ ${3} | ${'460f005b-d29d-43c1-9a08-099a7c7f08de'}
+ ${4} | ${'d8c89733-6ce1-4455-ae3d-f8aad6ee99f9'}
+ `('sets the file id properly { id: $id } on normal diff files', ({ fileIndex, id }) => {
+ const preppedFile = prepareRawDiffFile({
+ file: files[fileIndex],
+ allFiles: files,
+ });
+
+ expect(preppedFile.id).toBe(id);
+ });
+
+ it('does not set the `id` property for metadata diff files', () => {
+ const preppedFile = prepareRawDiffFile({
+ file: files[0],
+ allFiles: files,
+ meta: true,
+ });
+
+ expect(preppedFile).not.toHaveProp('id');
+ });
+
+ it('does not set the id property if the file is missing a `blob.id`', () => {
+ const fileMissingContentSha = { ...files[0] };
+
+ delete fileMissingContentSha.blob.id;
+
+ const preppedFile = prepareRawDiffFile({
+ file: fileMissingContentSha,
+ allFiles: files,
+ });
+
+ expect(preppedFile).not.toHaveProp('id');
+ });
+ });
+});
diff --git a/spec/frontend/diffs/utils/preferences_spec.js b/spec/frontend/diffs/utils/preferences_spec.js
new file mode 100644
index 00000000000..a48db1d7512
--- /dev/null
+++ b/spec/frontend/diffs/utils/preferences_spec.js
@@ -0,0 +1,40 @@
+import Cookies from 'js-cookie';
+import { getParameterValues } from '~/lib/utils/url_utility';
+
+import { fileByFile } from '~/diffs/utils/preferences';
+import {
+ DIFF_FILE_BY_FILE_COOKIE_NAME,
+ DIFF_VIEW_FILE_BY_FILE,
+ DIFF_VIEW_ALL_FILES,
+} from '~/diffs/constants';
+
+jest.mock('~/lib/utils/url_utility');
+
+describe('diffs preferences', () => {
+ describe('fileByFile', () => {
+ it.each`
+ result | preference | cookie | searchParam
+ ${false} | ${false} | ${undefined} | ${undefined}
+ ${true} | ${true} | ${undefined} | ${undefined}
+ ${true} | ${false} | ${DIFF_VIEW_FILE_BY_FILE} | ${undefined}
+ ${false} | ${true} | ${DIFF_VIEW_ALL_FILES} | ${undefined}
+ ${true} | ${false} | ${undefined} | ${[DIFF_VIEW_FILE_BY_FILE]}
+ ${false} | ${true} | ${undefined} | ${[DIFF_VIEW_ALL_FILES]}
+ ${true} | ${false} | ${DIFF_VIEW_FILE_BY_FILE} | ${[DIFF_VIEW_FILE_BY_FILE]}
+ ${true} | ${true} | ${DIFF_VIEW_ALL_FILES} | ${[DIFF_VIEW_FILE_BY_FILE]}
+ ${false} | ${false} | ${DIFF_VIEW_ALL_FILES} | ${[DIFF_VIEW_ALL_FILES]}
+ ${false} | ${true} | ${DIFF_VIEW_FILE_BY_FILE} | ${[DIFF_VIEW_ALL_FILES]}
+ `(
+ 'should return $result when { preference: $preference, cookie: $cookie, search: $searchParam }',
+ ({ result, preference, cookie, searchParam }) => {
+ if (cookie) {
+ Cookies.set(DIFF_FILE_BY_FILE_COOKIE_NAME, cookie);
+ }
+
+ getParameterValues.mockReturnValue(searchParam);
+
+ expect(fileByFile(preference)).toBe(result);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/editor/editor_lite_extension_base_spec.js b/spec/frontend/editor/editor_lite_extension_base_spec.js
new file mode 100644
index 00000000000..ff53640b096
--- /dev/null
+++ b/spec/frontend/editor/editor_lite_extension_base_spec.js
@@ -0,0 +1,44 @@
+import { ERROR_INSTANCE_REQUIRED_FOR_EXTENSION } from '~/editor/constants';
+import { EditorLiteExtension } from '~/editor/editor_lite_extension_base';
+
+describe('The basis for an Editor Lite extension', () => {
+ let ext;
+ const defaultOptions = { foo: 'bar' };
+
+ it.each`
+ description | instance | options
+ ${'accepts configuration options and instance'} | ${{}} | ${defaultOptions}
+ ${'leaves instance intact if no options are passed'} | ${{}} | ${undefined}
+ ${'does not fail if both instance and the options are omitted'} | ${undefined} | ${undefined}
+ ${'throws if only options are passed'} | ${undefined} | ${defaultOptions}
+ `('$description', ({ instance, options } = {}) => {
+ const originalInstance = { ...instance };
+
+ if (instance) {
+ if (options) {
+ Object.entries(options).forEach(prop => {
+ expect(instance[prop]).toBeUndefined();
+ });
+ // Both instance and options are passed
+ ext = new EditorLiteExtension({ instance, ...options });
+ Object.entries(options).forEach(([prop, value]) => {
+ expect(ext[prop]).toBeUndefined();
+ expect(instance[prop]).toBe(value);
+ });
+ } else {
+ ext = new EditorLiteExtension({ instance });
+ expect(instance).toEqual(originalInstance);
+ }
+ } else if (options) {
+ // Options are passed without instance
+ expect(() => {
+ ext = new EditorLiteExtension({ ...options });
+ }).toThrow(ERROR_INSTANCE_REQUIRED_FOR_EXTENSION);
+ } else {
+ // Neither options nor instance are passed
+ expect(() => {
+ ext = new EditorLiteExtension();
+ }).not.toThrow();
+ }
+ });
+});
diff --git a/spec/frontend/editor/editor_lite_spec.js b/spec/frontend/editor/editor_lite_spec.js
index 2968984df01..3a7680f6d17 100644
--- a/spec/frontend/editor/editor_lite_spec.js
+++ b/spec/frontend/editor/editor_lite_spec.js
@@ -1,6 +1,8 @@
+/* eslint-disable max-classes-per-file */
import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
import waitForPromises from 'helpers/wait_for_promises';
import Editor from '~/editor/editor_lite';
+import { EditorLiteExtension } from '~/editor/editor_lite_extension_base';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from '~/editor/constants';
@@ -242,17 +244,53 @@ describe('Base editor', () => {
describe('extensions', () => {
let instance;
- const foo1 = jest.fn();
- const foo2 = jest.fn();
- const bar = jest.fn();
- const MyExt1 = {
- foo: foo1,
+ const alphaRes = jest.fn();
+ const betaRes = jest.fn();
+ const fooRes = jest.fn();
+ const barRes = jest.fn();
+ class AlphaClass {
+ constructor() {
+ this.res = alphaRes;
+ }
+ alpha() {
+ return this?.nonExistentProp || alphaRes;
+ }
+ }
+ class BetaClass {
+ beta() {
+ return this?.nonExistentProp || betaRes;
+ }
+ }
+ class WithStaticMethod {
+ constructor({ instance: inst, ...options } = {}) {
+ Object.assign(inst, options);
+ }
+ static computeBoo(a) {
+ return a + 1;
+ }
+ boo() {
+ return WithStaticMethod.computeBoo(this.base);
+ }
+ }
+ class WithStaticMethodExtended extends EditorLiteExtension {
+ static computeBoo(a) {
+ return a + 1;
+ }
+ boo() {
+ return WithStaticMethodExtended.computeBoo(this.base);
+ }
+ }
+ const AlphaExt = new AlphaClass();
+ const BetaExt = new BetaClass();
+ const FooObjExt = {
+ foo() {
+ return fooRes;
+ },
};
- const MyExt2 = {
- bar,
- };
- const MyExt3 = {
- foo: foo2,
+ const BarObjExt = {
+ bar() {
+ return barRes;
+ },
};
describe('basic functionality', () => {
@@ -260,13 +298,6 @@ describe('Base editor', () => {
instance = editor.createInstance({ el: editorEl, blobPath, blobContent });
});
- it('is extensible with the extensions', () => {
- expect(instance.foo).toBeUndefined();
-
- instance.use(MyExt1);
- expect(instance.foo).toEqual(foo1);
- });
-
it('does not fail if no extensions supplied', () => {
const spy = jest.spyOn(global.console, 'error');
instance.use();
@@ -274,24 +305,80 @@ describe('Base editor', () => {
expect(spy).not.toHaveBeenCalled();
});
- it('is extensible with multiple extensions', () => {
- expect(instance.foo).toBeUndefined();
- expect(instance.bar).toBeUndefined();
+ it("does not extend instance with extension's constructor", () => {
+ expect(instance.constructor).toBeDefined();
+ const { constructor } = instance;
+
+ expect(AlphaExt.constructor).toBeDefined();
+ expect(AlphaExt.constructor).not.toEqual(constructor);
+
+ instance.use(AlphaExt);
+ expect(instance.constructor).toBe(constructor);
+ });
+
+ it.each`
+ type | extensions | methods | expectations
+ ${'ES6 classes'} | ${AlphaExt} | ${['alpha']} | ${[alphaRes]}
+ ${'multiple ES6 classes'} | ${[AlphaExt, BetaExt]} | ${['alpha', 'beta']} | ${[alphaRes, betaRes]}
+ ${'simple objects'} | ${FooObjExt} | ${['foo']} | ${[fooRes]}
+ ${'multiple simple objects'} | ${[FooObjExt, BarObjExt]} | ${['foo', 'bar']} | ${[fooRes, barRes]}
+ ${'combination of ES6 classes and objects'} | ${[AlphaExt, BarObjExt]} | ${['alpha', 'bar']} | ${[alphaRes, barRes]}
+ `('is extensible with $type', ({ extensions, methods, expectations } = {}) => {
+ methods.forEach(method => {
+ expect(instance[method]).toBeUndefined();
+ });
- instance.use([MyExt1, MyExt2]);
+ instance.use(extensions);
- expect(instance.foo).toEqual(foo1);
- expect(instance.bar).toEqual(bar);
+ methods.forEach(method => {
+ expect(instance[method]).toBeDefined();
+ });
+
+ expectations.forEach((expectation, i) => {
+ expect(instance[methods[i]].call()).toEqual(expectation);
+ });
});
+ it('does not extend instance with private data of an extension', () => {
+ const ext = new WithStaticMethod({ instance });
+ ext.staticMethod = () => {
+ return 'foo';
+ };
+ ext.staticProp = 'bar';
+
+ expect(instance.boo).toBeUndefined();
+ expect(instance.staticMethod).toBeUndefined();
+ expect(instance.staticProp).toBeUndefined();
+
+ instance.use(ext);
+
+ expect(instance.boo).toBeDefined();
+ expect(instance.staticMethod).toBeUndefined();
+ expect(instance.staticProp).toBeUndefined();
+ });
+
+ it.each([WithStaticMethod, WithStaticMethodExtended])(
+ 'properly resolves data for an extension with private data',
+ ExtClass => {
+ const base = 1;
+ expect(instance.base).toBeUndefined();
+ expect(instance.boo).toBeUndefined();
+
+ const ext = new ExtClass({ instance, base });
+
+ instance.use(ext);
+ expect(instance.base).toBe(1);
+ expect(instance.boo()).toBe(2);
+ },
+ );
+
it('uses the last definition of a method in case of an overlap', () => {
- instance.use([MyExt1, MyExt2, MyExt3]);
- expect(instance).toEqual(
- expect.objectContaining({
- foo: foo2,
- bar,
- }),
- );
+ const FooObjExt2 = { foo: 'foo2' };
+ instance.use([FooObjExt, BarObjExt, FooObjExt2]);
+ expect(instance).toMatchObject({
+ foo: 'foo2',
+ ...BarObjExt,
+ });
});
it('correctly resolves references withing extensions', () => {
@@ -396,15 +483,15 @@ describe('Base editor', () => {
});
it('extends all instances if no specific instance is passed', () => {
- editor.use(MyExt1);
- expect(inst1.foo).toEqual(foo1);
- expect(inst2.foo).toEqual(foo1);
+ editor.use(AlphaExt);
+ expect(inst1.alpha()).toEqual(alphaRes);
+ expect(inst2.alpha()).toEqual(alphaRes);
});
it('extends specific instance if it has been passed', () => {
- editor.use(MyExt1, inst2);
- expect(inst1.foo).toBeUndefined();
- expect(inst2.foo).toEqual(foo1);
+ editor.use(AlphaExt, inst2);
+ expect(inst1.alpha).toBeUndefined();
+ expect(inst2.alpha()).toEqual(alphaRes);
});
});
});
diff --git a/spec/frontend/editor/editor_markdown_ext_spec.js b/spec/frontend/editor/editor_markdown_ext_spec.js
index 30ab29aad35..b432d4d66ad 100644
--- a/spec/frontend/editor/editor_markdown_ext_spec.js
+++ b/spec/frontend/editor/editor_markdown_ext_spec.js
@@ -1,6 +1,6 @@
import { Range, Position } from 'monaco-editor';
import EditorLite from '~/editor/editor_lite';
-import EditorMarkdownExtension from '~/editor/editor_markdown_ext';
+import { EditorMarkdownExtension } from '~/editor/editor_markdown_ext';
describe('Markdown Extension for Editor Lite', () => {
let editor;
@@ -31,7 +31,7 @@ describe('Markdown Extension for Editor Lite', () => {
blobPath: filePath,
blobContent: text,
});
- editor.use(EditorMarkdownExtension);
+ editor.use(new EditorMarkdownExtension());
});
afterEach(() => {
diff --git a/spec/frontend/environments/environment_actions_spec.js b/spec/frontend/environments/environment_actions_spec.js
index d305f5e90bd..cc5153d6eba 100644
--- a/spec/frontend/environments/environment_actions_spec.js
+++ b/spec/frontend/environments/environment_actions_spec.js
@@ -1,51 +1,69 @@
-import { shallowMount } from '@vue/test-utils';
+import { shallowMount, mount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
-import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
+import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import eventHub from '~/environments/event_hub';
import EnvironmentActions from '~/environments/components/environment_actions.vue';
+const scheduledJobAction = {
+ name: 'scheduled action',
+ playPath: `${TEST_HOST}/scheduled/job/action`,
+ playable: true,
+ scheduledAt: '2063-04-05T00:42:00Z',
+};
+
+const expiredJobAction = {
+ name: 'expired action',
+ playPath: `${TEST_HOST}/expired/job/action`,
+ playable: true,
+ scheduledAt: '2018-10-05T08:23:00Z',
+};
+
describe('EnvironmentActions Component', () => {
- let vm;
+ let wrapper;
- const findEnvironmentActionsButton = () => vm.find('[data-testid="environment-actions-button"]');
+ const findEnvironmentActionsButton = () =>
+ wrapper.find('[data-testid="environment-actions-button"]');
- beforeEach(() => {
- vm = shallowMount(EnvironmentActions, {
- propsData: { actions: [] },
+ function createComponent(props, { mountFn = shallowMount } = {}) {
+ wrapper = mountFn(EnvironmentActions, {
+ propsData: { actions: [], ...props },
directives: {
GlTooltip: createMockDirective(),
},
});
- });
+ }
+
+ function createComponentWithScheduledJobs(opts = {}) {
+ return createComponent({ actions: [scheduledJobAction, expiredJobAction] }, opts);
+ }
+
+ const findDropdownItem = action => {
+ const buttons = wrapper.findAll(GlDropdownItem);
+ return buttons.filter(button => button.text().startsWith(action.name)).at(0);
+ };
afterEach(() => {
- vm.destroy();
+ wrapper.destroy();
+ wrapper = null;
});
it('should render a dropdown button with 2 icons', () => {
- expect(vm.find('.dropdown-new').findAll(GlIcon).length).toBe(2);
+ createComponent({}, { mountFn: mount });
+ expect(wrapper.find(GlDropdown).findAll(GlIcon).length).toBe(2);
});
it('should render a dropdown button with aria-label description', () => {
- expect(vm.find('.dropdown-new').attributes('aria-label')).toEqual('Deploy to...');
+ createComponent();
+ expect(wrapper.find(GlDropdown).attributes('aria-label')).toBe('Deploy to...');
});
it('should render a tooltip', () => {
+ createComponent();
const tooltip = getBinding(findEnvironmentActionsButton().element, 'gl-tooltip');
expect(tooltip).toBeDefined();
});
- describe('is loading', () => {
- beforeEach(() => {
- vm.setData({ isLoading: true });
- });
-
- it('should render a dropdown button with a loading icon', () => {
- expect(vm.findAll(GlLoadingIcon).length).toBe(1);
- });
- });
-
describe('manual actions', () => {
const actions = [
{
@@ -64,68 +82,71 @@ describe('EnvironmentActions Component', () => {
];
beforeEach(() => {
- vm.setProps({ actions });
+ createComponent({ actions });
});
it('should render a dropdown with the provided list of actions', () => {
- expect(vm.findAll('.dropdown-menu li').length).toEqual(actions.length);
+ expect(wrapper.findAll(GlDropdownItem)).toHaveLength(actions.length);
});
it("should render a disabled action when it's not playable", () => {
- expect(vm.find('.dropdown-menu li:last-child gl-button-stub').props('disabled')).toBe(true);
+ const dropdownItems = wrapper.findAll(GlDropdownItem);
+ const lastDropdownItem = dropdownItems.at(dropdownItems.length - 1);
+ expect(lastDropdownItem.attributes('disabled')).toBe('true');
});
});
describe('scheduled jobs', () => {
- const scheduledJobAction = {
- name: 'scheduled action',
- playPath: `${TEST_HOST}/scheduled/job/action`,
- playable: true,
- scheduledAt: '2063-04-05T00:42:00Z',
- };
- const expiredJobAction = {
- name: 'expired action',
- playPath: `${TEST_HOST}/expired/job/action`,
- playable: true,
- scheduledAt: '2018-10-05T08:23:00Z',
- };
- const findDropdownItem = action => {
- const buttons = vm.findAll('.dropdown-menu li gl-button-stub');
- return buttons.filter(button => button.text().startsWith(action.name)).at(0);
+ let emitSpy;
+
+ const clickAndConfirm = async ({ confirm = true } = {}) => {
+ jest.spyOn(window, 'confirm').mockImplementation(() => confirm);
+
+ findDropdownItem(scheduledJobAction).vm.$emit('click');
+ await wrapper.vm.$nextTick();
};
beforeEach(() => {
+ emitSpy = jest.fn();
+ eventHub.$on('postAction', emitSpy);
jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
- vm.setProps({ actions: [scheduledJobAction, expiredJobAction] });
});
- it('emits postAction event after confirming', () => {
- const emitSpy = jest.fn();
- eventHub.$on('postAction', emitSpy);
- jest.spyOn(window, 'confirm').mockImplementation(() => true);
+ describe('when postAction event is confirmed', () => {
+ beforeEach(async () => {
+ createComponentWithScheduledJobs({ mountFn: mount });
+ clickAndConfirm();
+ });
- findDropdownItem(scheduledJobAction).vm.$emit('click');
+ it('emits postAction event', () => {
+ expect(window.confirm).toHaveBeenCalled();
+ expect(emitSpy).toHaveBeenCalledWith({ endpoint: scheduledJobAction.playPath });
+ });
- expect(window.confirm).toHaveBeenCalled();
- expect(emitSpy).toHaveBeenCalledWith({ endpoint: scheduledJobAction.playPath });
+ it('should render a dropdown button with a loading icon', () => {
+ expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true);
+ });
});
- it('does not emit postAction event if confirmation is cancelled', () => {
- const emitSpy = jest.fn();
- eventHub.$on('postAction', emitSpy);
- jest.spyOn(window, 'confirm').mockImplementation(() => false);
-
- findDropdownItem(scheduledJobAction).vm.$emit('click');
+ describe('when postAction event is denied', () => {
+ beforeEach(() => {
+ createComponentWithScheduledJobs({ mountFn: mount });
+ clickAndConfirm({ confirm: false });
+ });
- expect(window.confirm).toHaveBeenCalled();
- expect(emitSpy).not.toHaveBeenCalled();
+ it('does not emit postAction event if confirmation is cancelled', () => {
+ expect(window.confirm).toHaveBeenCalled();
+ expect(emitSpy).not.toHaveBeenCalled();
+ });
});
it('displays the remaining time in the dropdown', () => {
+ createComponentWithScheduledJobs();
expect(findDropdownItem(scheduledJobAction).text()).toContain('24:00:00');
});
it('displays 00:00:00 for expired jobs in the dropdown', () => {
+ createComponentWithScheduledJobs();
expect(findDropdownItem(expiredJobAction).text()).toContain('00:00:00');
});
});
diff --git a/spec/frontend/environments/environment_item_spec.js b/spec/frontend/environments/environment_item_spec.js
index 1b429783821..bc692352103 100644
--- a/spec/frontend/environments/environment_item_spec.js
+++ b/spec/frontend/environments/environment_item_spec.js
@@ -1,3 +1,4 @@
+import { cloneDeep } from 'lodash';
import { mount } from '@vue/test-utils';
import { format } from 'timeago.js';
import EnvironmentItem from '~/environments/components/environment_item.vue';
@@ -30,6 +31,11 @@ describe('Environment item', () => {
});
const findAutoStop = () => wrapper.find('.js-auto-stop');
+ const findUpcomingDeployment = () => wrapper.find('[data-testid="upcoming-deployment"]');
+ const findUpcomingDeploymentContent = () =>
+ wrapper.find('[data-testid="upcoming-deployment-content"]');
+ const findUpcomingDeploymentStatusLink = () =>
+ wrapper.find('[data-testid="upcoming-deployment-status-link"]');
afterEach(() => {
wrapper.destroy();
@@ -87,6 +93,72 @@ describe('Environment item', () => {
});
});
+ describe('When the envionment has an upcoming deployment', () => {
+ describe('When the upcoming deployment has a deployable', () => {
+ it('should render the build ID and user', () => {
+ expect(findUpcomingDeploymentContent().text()).toMatchInterpolatedText(
+ '#27 by upcoming-username',
+ );
+ });
+
+ it('should render a status icon with a link and tooltip', () => {
+ expect(findUpcomingDeploymentStatusLink().exists()).toBe(true);
+
+ expect(findUpcomingDeploymentStatusLink().attributes().href).toBe(
+ '/root/environment-test/-/jobs/892',
+ );
+
+ expect(findUpcomingDeploymentStatusLink().attributes().title).toBe(
+ 'Deployment running',
+ );
+ });
+ });
+
+ describe('When the deployment does not have a deployable', () => {
+ beforeEach(() => {
+ const environmentWithoutDeployable = cloneDeep(environment);
+ delete environmentWithoutDeployable.upcoming_deployment.deployable;
+
+ factory({
+ propsData: {
+ model: environmentWithoutDeployable,
+ canReadEnvironment: true,
+ tableData,
+ },
+ });
+ });
+
+ it('should still renders the build ID and user', () => {
+ expect(findUpcomingDeploymentContent().text()).toMatchInterpolatedText(
+ '#27 by upcoming-username',
+ );
+ });
+
+ it('should not render the status icon', () => {
+ expect(findUpcomingDeploymentStatusLink().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('Without upcoming deployment', () => {
+ beforeEach(() => {
+ const environmentWithoutUpcomingDeployment = cloneDeep(environment);
+ delete environmentWithoutUpcomingDeployment.upcoming_deployment;
+
+ factory({
+ propsData: {
+ model: environmentWithoutUpcomingDeployment,
+ canReadEnvironment: true,
+ tableData,
+ },
+ });
+ });
+
+ it('should not render anything in the upcoming deployment column', () => {
+ expect(findUpcomingDeploymentContent().exists()).toBe(false);
+ });
+ });
+
describe('Without auto-stop date', () => {
beforeEach(() => {
factory({
@@ -209,6 +281,10 @@ describe('Environment item', () => {
it('should render the number of children in a badge', () => {
expect(wrapper.find('.folder-name .badge').text()).toContain(folder.size);
});
+
+ it('should not render the "Upcoming deployment" column', () => {
+ expect(findUpcomingDeployment().exists()).toBe(false);
+ });
});
describe('When environment can be deleted', () => {
diff --git a/spec/frontend/environments/mock_data.js b/spec/frontend/environments/mock_data.js
index 77c5dad0bbf..e7b99c8688c 100644
--- a/spec/frontend/environments/mock_data.js
+++ b/spec/frontend/environments/mock_data.js
@@ -86,6 +86,98 @@ const environment = {
],
deployed_at: '2016-11-29T18:11:58.430Z',
},
+ upcoming_deployment: {
+ id: 82,
+ iid: 27,
+ sha: '1132df044b73943943c949e7ac2c2f120a89bf59',
+ ref: {
+ name: 'master',
+ ref_path: '/root/environment-test/-/tree/master',
+ },
+ status: 'running',
+ created_at: '2020-12-04T19:57:49.514Z',
+ deployed_at: null,
+ tag: false,
+ 'last?': false,
+ user: {
+ id: 1,
+ name: 'Upcoming Name',
+ username: 'upcoming-username',
+ state: 'active',
+ avatar_url: 'http://0.0.0.0:3000/uploads/-/system/user/avatar/2/avatar.png',
+ web_url: 'http://0.0.0.0:3000/upcoming-username',
+ show_status: false,
+ path: '/upcoming-username',
+ },
+ deployable: {
+ id: 1310,
+ name: 'deploy_to_development',
+ started: '2020-12-04T19:58:10.806Z',
+ archived: false,
+ build_path: '/root/environment-test/-/jobs/892',
+ cancel_path:
+ '/root/environment-test/-/jobs/892/cancel?continue%5Bto%5D=%2Froot%2Fenvironment-test%2F-%2Fjobs%2F892',
+ playable: false,
+ scheduled: false,
+ created_at: '2020-12-04T19:57:49.455Z',
+ updated_at: '2020-12-04T19:58:10.809Z',
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ tooltip: 'running',
+ has_details: true,
+ details_path: '/root/environment-test/-/jobs/892',
+ illustration: {
+ image:
+ '/assets/illustrations/skipped-job_empty-29a8a37d8a61d1b6f68cf3484f9024e53cd6eb95e28eae3554f8011a1146bf27.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png',
+ action: {
+ icon: 'cancel',
+ title: 'Cancel',
+ path: '/root/environment-test/-/jobs/892/cancel',
+ method: 'post',
+ button_title: 'Cancel this job',
+ },
+ },
+ },
+ commit: {
+ id: '1132df044b73943943c949e7ac2c2f120a89bf59',
+ short_id: '1132df04',
+ created_at: '2020-12-01T15:46:26.000-05:00',
+ parent_ids: ['e0808dee2a5877563ec140e65d8b41908f90098c'],
+ title: 'Update .gitlab-ci.yml',
+ message: 'Update .gitlab-ci.yml',
+ author_name: 'Upcoming Name',
+ author_email: 'admin@example.com',
+ authored_date: '2020-12-01T15:46:26.000-05:00',
+ committer_name: 'Upcoming Name',
+ committer_email: 'admin@example.com',
+ committed_date: '2020-12-01T15:46:26.000-05:00',
+ web_url:
+ 'http://0.0.0.0:3000/root/environment-test/-/commit/1132df044b73943943c949e7ac2c2f120a89bf59',
+ author: {
+ id: 1,
+ name: 'Upcoming Name',
+ username: 'upcoming-username',
+ state: 'active',
+ avatar_url: 'http://0.0.0.0:3000/uploads/-/system/user/avatar/2/avatar.png',
+ web_url: 'http://0.0.0.0:3000/upcoming-username',
+ show_status: false,
+ path: '/upcoming-username',
+ },
+ author_gravatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ commit_url:
+ 'http://0.0.0.0:3000/root/environment-test/-/commit/1132df044b73943943c949e7ac2c2f120a89bf59',
+ commit_path: '/root/environment-test/-/commit/1132df044b73943943c949e7ac2c2f120a89bf59',
+ },
+ },
has_stop_action: true,
environment_path: 'root/ci-folders/environments/31',
log_path: 'root/ci-folders/environments/31/logs',
@@ -156,6 +248,11 @@ const tableData = {
title: 'Updated',
spacing: 'section-10',
},
+ upcoming: {
+ title: 'Upcoming',
+ mobileTitle: 'Upcoming deployment',
+ spacing: 'section-10',
+ },
autoStop: {
title: 'Auto stop in',
spacing: 'section-5',
diff --git a/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js b/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js
index 67f4bee766b..06b9385b112 100644
--- a/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js
+++ b/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js
@@ -1,7 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import { GlModal, GlSprintf } from '@gitlab/ui';
+import { GlModal, GlSprintf, GlAlert } from '@gitlab/ui';
import Component from '~/feature_flags/components/configure_feature_flags_modal.vue';
-import Callout from '~/vue_shared/components/callout.vue';
describe('Configure Feature Flags Modal', () => {
const mockEvent = { preventDefault: jest.fn() };
@@ -36,8 +35,8 @@ describe('Configure Feature Flags Modal', () => {
const findGlModal = () => wrapper.find(GlModal);
const findPrimaryAction = () => findGlModal().props('actionPrimary');
const findProjectNameInput = () => wrapper.find('#project_name_verification');
- const findDangerCallout = () =>
- wrapper.findAll(Callout).filter(c => c.props('category') === 'danger');
+ const findDangerGlAlert = () =>
+ wrapper.findAll(GlAlert).filter(c => c.props('variant') === 'danger');
describe('idle', () => {
afterEach(() => wrapper.destroy());
@@ -86,10 +85,10 @@ describe('Configure Feature Flags Modal', () => {
);
});
- it('should display one and only one danger callout', () => {
- const dangerCallout = findDangerCallout();
- expect(dangerCallout.length).toBe(1);
- expect(dangerCallout.at(0).props('message')).toMatch(/Regenerating the instance ID/);
+ it('should display one and only one danger alert', () => {
+ const dangerGlAlert = findDangerGlAlert();
+ expect(dangerGlAlert.length).toBe(1);
+ expect(dangerGlAlert.at(0).text()).toMatch(/Regenerating the instance ID/);
});
it('should display a message asking to fill the project name', () => {
@@ -130,7 +129,7 @@ describe('Configure Feature Flags Modal', () => {
});
it('should not display regenerating instance ID', async () => {
- expect(findDangerCallout().exists()).toBe(false);
+ expect(findDangerGlAlert().exists()).toBe(false);
});
it('should disable the project name input', async () => {
diff --git a/spec/frontend/feature_flags/components/edit_feature_flag_spec.js b/spec/frontend/feature_flags/components/edit_feature_flag_spec.js
index 6a394251060..f8e25925774 100644
--- a/spec/frontend/feature_flags/components/edit_feature_flag_spec.js
+++ b/spec/frontend/feature_flags/components/edit_feature_flag_spec.js
@@ -4,7 +4,7 @@ import MockAdapter from 'axios-mock-adapter';
import { GlToggle, GlAlert } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants';
import { mockTracking } from 'helpers/tracking_helper';
-import { LEGACY_FLAG, NEW_VERSION_FLAG, NEW_FLAG_ALERT } from '~/feature_flags/constants';
+import { LEGACY_FLAG, NEW_VERSION_FLAG } from '~/feature_flags/constants';
import Form from '~/feature_flags/components/form.vue';
import createStore from '~/feature_flags/store/edit';
import EditFeatureFlag from '~/feature_flags/components/edit_feature_flag.vue';
@@ -37,9 +37,6 @@ describe('Edit feature flag form', () => {
showUserCallout: true,
userCalloutId,
userCalloutsPath,
- glFeatures: {
- featureFlagsNewVersion: true,
- },
...opts,
},
});
@@ -151,33 +148,4 @@ describe('Edit feature flag form', () => {
});
});
});
-
- describe('without new version flags', () => {
- beforeEach(() => factory({ glFeatures: { featureFlagsNewVersion: false } }));
-
- it('should alert users that feature flags are changing soon', () => {
- expect(findAlert().text()).toBe(NEW_FLAG_ALERT);
- });
- });
-
- describe('dismissing new version alert', () => {
- beforeEach(() => {
- factory({ glFeatures: { featureFlagsNewVersion: false } });
- mock.onPost(userCalloutsPath, { feature_name: userCalloutId }).reply(200);
- findAlert().vm.$emit('dismiss');
- return wrapper.vm.$nextTick();
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('should hide the alert', () => {
- expect(findAlert().exists()).toBe(false);
- });
-
- it('should send the dismissal event', () => {
- expect(mock.history.post.length).toBe(1);
- });
- });
});
diff --git a/spec/frontend/feature_flags/components/feature_flags_tab_spec.js b/spec/frontend/feature_flags/components/feature_flags_tab_spec.js
index bc90c5ceb2d..23cc7045d1f 100644
--- a/spec/frontend/feature_flags/components/feature_flags_tab_spec.js
+++ b/spec/frontend/feature_flags/components/feature_flags_tab_spec.js
@@ -12,6 +12,7 @@ const DEFAULT_PROPS = {
errorTitle: 'test title',
emptyState: true,
emptyTitle: 'test empty',
+ emptyDescription: 'empty description',
};
const DEFAULT_PROVIDE = {
@@ -115,9 +116,7 @@ describe('feature_flags/components/feature_flags_tab.vue', () => {
it('should show an empty state if it is empty', () => {
expect(emptyState.text()).toContain(DEFAULT_PROPS.emptyTitle);
- expect(emptyState.text()).toContain(
- 'Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality.',
- );
+ expect(emptyState.text()).toContain(DEFAULT_PROPS.emptyDescription);
expect(emptyState.props('svgPath')).toBe(DEFAULT_PROVIDE.errorStateSvgPath);
expect(emptyStateLink.attributes('href')).toBe(DEFAULT_PROVIDE.featureFlagsHelpPagePath);
expect(emptyStateLink.text()).toBe('More information');
diff --git a/spec/frontend/feature_flags/components/new_feature_flag_spec.js b/spec/frontend/feature_flags/components/new_feature_flag_spec.js
index dbc6e03d922..e317ac4b092 100644
--- a/spec/frontend/feature_flags/components/new_feature_flag_spec.js
+++ b/spec/frontend/feature_flags/components/new_feature_flag_spec.js
@@ -1,17 +1,11 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import MockAdapter from 'axios-mock-adapter';
import { GlAlert } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants';
import Form from '~/feature_flags/components/form.vue';
import createStore from '~/feature_flags/store/new';
import NewFeatureFlag from '~/feature_flags/components/new_feature_flag.vue';
-import {
- ROLLOUT_STRATEGY_ALL_USERS,
- DEFAULT_PERCENT_ROLLOUT,
- NEW_FLAG_ALERT,
-} from '~/feature_flags/constants';
-import axios from '~/lib/utils/axios_utils';
+import { ROLLOUT_STRATEGY_ALL_USERS, DEFAULT_PERCENT_ROLLOUT } from '~/feature_flags/constants';
import { allUsersStrategy } from '../mock_data';
const userCalloutId = 'feature_flags_new_version';
@@ -42,9 +36,6 @@ describe('New feature flag form', () => {
userCalloutsPath,
environmentsEndpoint: 'environments.json',
projectId: '8',
- glFeatures: {
- featureFlagsNewVersion: true,
- },
...opts,
},
});
@@ -58,8 +49,6 @@ describe('New feature flag form', () => {
wrapper.destroy();
});
- const findAlert = () => wrapper.find(GlAlert);
-
describe('with error', () => {
it('should render the error', () => {
store.dispatch('receiveCreateFeatureFlagError', { message: ['The name is required'] });
@@ -101,36 +90,4 @@ describe('New feature flag form', () => {
expect(strategies).toEqual([allUsersStrategy]);
});
-
- describe('without new version flags', () => {
- beforeEach(() => factory({ glFeatures: { featureFlagsNewVersion: false } }));
-
- it('should alert users that feature flags are changing soon', () => {
- expect(findAlert().text()).toBe(NEW_FLAG_ALERT);
- });
- });
-
- describe('dismissing new version alert', () => {
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onPost(userCalloutsPath, { feature_name: userCalloutId }).reply(200);
- factory({ glFeatures: { featureFlagsNewVersion: false } });
- findAlert().vm.$emit('dismiss');
- return wrapper.vm.$nextTick();
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('should hide the alert', () => {
- expect(findAlert().exists()).toBe(false);
- });
-
- it('should send the dismissal event', () => {
- expect(mock.history.post.length).toBe(1);
- });
- });
});
diff --git a/spec/frontend/filtered_search/filtered_search_manager_spec.js b/spec/frontend/filtered_search/filtered_search_manager_spec.js
index 5c37d986ef1..b1c299ba91f 100644
--- a/spec/frontend/filtered_search/filtered_search_manager_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_manager_spec.js
@@ -69,7 +69,7 @@ describe('Filtered Search Manager', () => {
${FilteredSearchSpecHelper.createInputHTML(placeholder)}
</ul>
<button class="clear-search" type="button">
- <i class="fa fa-times"></i>
+ <svg class="s16 clear-search-icon" data-testid="close-icon"><use xlink:href="icons.svg#close" /></svg>
</button>
</form>
</div>
diff --git a/spec/frontend/fixtures/abuse_reports.rb b/spec/frontend/fixtures/abuse_reports.rb
index 48b055fcda5..f5524a10033 100644
--- a/spec/frontend/fixtures/abuse_reports.rb
+++ b/spec/frontend/fixtures/abuse_reports.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
+ include AdminModeHelper
let(:admin) { create(:admin) }
let!(:abuse_report) { create(:abuse_report) }
@@ -18,6 +19,7 @@ RSpec.describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :co
before do
sign_in(admin)
+ enable_admin_mode!(admin)
end
it 'abuse_reports/abuse_reports_list.html' do
diff --git a/spec/frontend/fixtures/admin_users.rb b/spec/frontend/fixtures/admin_users.rb
index f068ada53e1..e0fecbdb1aa 100644
--- a/spec/frontend/fixtures/admin_users.rb
+++ b/spec/frontend/fixtures/admin_users.rb
@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
include StubENV
include JavaScriptFixturesHelpers
+ include AdminModeHelper
let(:admin) { create(:admin) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
+ enable_admin_mode!(admin)
end
render_views
diff --git a/spec/frontend/fixtures/application_settings.rb b/spec/frontend/fixtures/application_settings.rb
index 6156e6a43bc..ebccecb32ba 100644
--- a/spec/frontend/fixtures/application_settings.rb
+++ b/spec/frontend/fixtures/application_settings.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :controller do
include StubENV
include JavaScriptFixturesHelpers
+ include AdminModeHelper
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
@@ -13,6 +14,7 @@ RSpec.describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', ty
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
+ enable_admin_mode!(admin)
end
render_views
diff --git a/spec/frontend/fixtures/autocomplete_sources.rb b/spec/frontend/fixtures/autocomplete_sources.rb
index 8858d69a939..9ff0f959c11 100644
--- a/spec/frontend/fixtures/autocomplete_sources.rb
+++ b/spec/frontend/fixtures/autocomplete_sources.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let_it_be(:admin) { create(:admin) }
+ let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, name: 'frontend-fixtures') }
let_it_be(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') }
let_it_be(:issue) { create(:issue, project: project) }
@@ -15,7 +15,8 @@ RSpec.describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)',
end
before do
- sign_in(admin)
+ group.add_owner(user)
+ sign_in(user)
end
it 'autocomplete_sources/labels.json' do
diff --git a/spec/frontend/fixtures/blob.rb b/spec/frontend/fixtures/blob.rb
index a365ee805af..b112886b2ca 100644
--- a/spec/frontend/fixtures/blob.rb
+++ b/spec/frontend/fixtures/blob.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
+ let(:user) { project.owner }
render_views
@@ -16,7 +16,7 @@ RSpec.describe Projects::BlobController, '(JavaScript fixtures)', type: :control
end
before do
- sign_in(admin)
+ sign_in(user)
allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
end
diff --git a/spec/frontend/fixtures/boards.rb b/spec/frontend/fixtures/boards.rb
deleted file mode 100644
index 90e2ca4db63..00000000000
--- a/spec/frontend/fixtures/boards.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'boards-project') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('boards/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'boards/show.html' do
- get(:index, params: {
- namespace_id: project.namespace,
- project_id: project
- })
-
- expect(response).to be_successful
- end
-end
diff --git a/spec/frontend/fixtures/branches.rb b/spec/frontend/fixtures/branches.rb
index df2d1af7ecf..f3b3633347d 100644
--- a/spec/frontend/fixtures/branches.rb
+++ b/spec/frontend/fixtures/branches.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.describe 'Branches (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- let_it_be(:admin) { create(:admin) }
let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let_it_be(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
+ let_it_be(:user) { project.owner }
before(:all) do
clean_frontend_fixtures('branches/')
@@ -22,7 +22,7 @@ RSpec.describe 'Branches (JavaScript fixtures)' do
render_views
before do
- sign_in(admin)
+ sign_in(user)
end
it 'branches/new_branch.html' do
@@ -44,7 +44,7 @@ RSpec.describe 'Branches (JavaScript fixtures)' do
# - "master": default, protected
# - "markdown": non-default, protected
# - "many_files": non-default, not protected
- get api("/projects/#{project.id}/repository/branches?search=ma", admin)
+ get api("/projects/#{project.id}/repository/branches?search=ma", user)
expect(response).to be_successful
end
diff --git a/spec/frontend/fixtures/clusters.rb b/spec/frontend/fixtures/clusters.rb
index d0940c7dc7f..b37aa137504 100644
--- a/spec/frontend/fixtures/clusters.rb
+++ b/spec/frontend/fixtures/clusters.rb
@@ -5,10 +5,10 @@ require 'spec_helper'
RSpec.describe Projects::ClustersController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace) }
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+ let(:user) { project.owner }
render_views
@@ -17,7 +17,7 @@ RSpec.describe Projects::ClustersController, '(JavaScript fixtures)', type: :con
end
before do
- sign_in(admin)
+ sign_in(user)
end
after do
diff --git a/spec/frontend/fixtures/commit.rb b/spec/frontend/fixtures/commit.rb
index 9175a757b73..ff62a8286fc 100644
--- a/spec/frontend/fixtures/commit.rb
+++ b/spec/frontend/fixtures/commit.rb
@@ -6,14 +6,12 @@ RSpec.describe 'Commit (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
let_it_be(:project) { create(:project, :repository) }
- let_it_be(:user) { create(:user) }
+ let_it_be(:user) { project.owner }
let_it_be(:commit) { project.commit("master") }
before(:all) do
clean_frontend_fixtures('commit/')
clean_frontend_fixtures('api/commits/')
-
- project.add_maintainer(user)
end
before do
diff --git a/spec/frontend/fixtures/deploy_keys.rb b/spec/frontend/fixtures/deploy_keys.rb
index e87600e9d24..5c24c071792 100644
--- a/spec/frontend/fixtures/deploy_keys.rb
+++ b/spec/frontend/fixtures/deploy_keys.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
+ include AdminModeHelper
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
@@ -17,7 +18,10 @@ RSpec.describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :c
end
before do
+ # Using an admin for these fixtures because they are used for verifying a frontend
+ # component that would normally get its data from `Admin::DeployKeysController`
sign_in(admin)
+ enable_admin_mode!(admin)
end
after do
diff --git a/spec/frontend/fixtures/emojis.rb b/spec/frontend/fixtures/emojis.rb
deleted file mode 100644
index b95c7632917..00000000000
--- a/spec/frontend/fixtures/emojis.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Emojis (JavaScript fixtures)', type: :request do
- include JavaScriptFixturesHelpers
-
- before(:all) do
- clean_frontend_fixtures('emojis/')
- end
-
- it 'emojis/emojis.json' do |example|
- get '/-/emojis/1/emojis.json'
-
- expect(response).to be_successful
- end
-end
diff --git a/spec/frontend/fixtures/freeze_period.rb b/spec/frontend/fixtures/freeze_period.rb
index 193bd0c3ef2..09e4f969e1d 100644
--- a/spec/frontend/fixtures/freeze_period.rb
+++ b/spec/frontend/fixtures/freeze_period.rb
@@ -4,10 +4,10 @@ require 'spec_helper'
RSpec.describe 'Freeze Periods (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- include Ci::PipelineSchedulesHelper
+ include TimeZoneHelper
- let_it_be(:admin) { create(:admin) }
let_it_be(:project) { create(:project, :repository, path: 'freeze-periods-project') }
+ let_it_be(:user) { project.owner }
before(:all) do
clean_frontend_fixtures('api/freeze-periods/')
@@ -34,16 +34,18 @@ RSpec.describe 'Freeze Periods (JavaScript fixtures)' do
create(:ci_freeze_period, project: project, freeze_start: '0 12 * * 1-5', freeze_end: '0 1 5 * *', cron_timezone: 'Etc/UTC')
create(:ci_freeze_period, project: project, freeze_start: '0 12 * * 1-5', freeze_end: '0 16 * * 6', cron_timezone: 'Europe/Berlin')
- get api("/projects/#{project.id}/freeze_periods", admin)
+ get api("/projects/#{project.id}/freeze_periods", user)
expect(response).to be_successful
end
end
- describe Ci::PipelineSchedulesHelper, '(JavaScript fixtures)' do
+ describe TimeZoneHelper, '(JavaScript fixtures)' do
let(:response) { timezone_data.to_json }
it 'api/freeze-periods/timezone_data.json' do
+ # Looks empty but does things
+ # More info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38525/diffs#note_391048415
end
end
end
diff --git a/spec/frontend/fixtures/groups.rb b/spec/frontend/fixtures/groups.rb
index 9f0b2c73c93..42aad9f187e 100644
--- a/spec/frontend/fixtures/groups.rb
+++ b/spec/frontend/fixtures/groups.rb
@@ -5,20 +5,20 @@ require 'spec_helper'
RSpec.describe 'Groups (JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
+ let(:user) { create(:user) }
let(:group) { create(:group, name: 'frontend-fixtures-group', runners_token: 'runnerstoken:intabulasreferre')}
- render_views
-
before(:all) do
clean_frontend_fixtures('groups/')
end
before do
- group.add_maintainer(admin)
- sign_in(admin)
+ group.add_owner(user)
+ sign_in(user)
end
+ render_views
+
describe GroupsController, '(JavaScript fixtures)', type: :controller do
it 'groups/edit.html' do
get :edit, params: { id: group }
diff --git a/spec/frontend/fixtures/issues.rb b/spec/frontend/fixtures/issues.rb
index baea87be45f..a027247bd0d 100644
--- a/spec/frontend/fixtures/issues.rb
+++ b/spec/frontend/fixtures/issues.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin, feed_token: 'feedtoken:coldfeed') }
+ let(:user) { create(:user, feed_token: 'feedtoken:coldfeed') }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') }
@@ -16,9 +16,8 @@ RSpec.describe Projects::IssuesController, '(JavaScript fixtures)', type: :contr
end
before do
- stub_feature_flags(vue_issue_header: false)
-
- sign_in(admin)
+ project.add_maintainer(user)
+ sign_in(user)
end
after do
@@ -42,17 +41,6 @@ RSpec.describe Projects::IssuesController, '(JavaScript fixtures)', type: :contr
render_issue(create(:closed_issue, project: project))
end
- it 'issues/issue-with-task-list.html' do
- issue = create(:issue, project: project, description: '- [ ] Task List Item')
- render_issue(issue)
- end
-
- it 'issues/issue_with_comment.html' do
- issue = create(:issue, project: project)
- create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save
- render_issue(issue)
- end
-
it 'issues/issue_list.html' do
create(:issue, project: project)
diff --git a/spec/frontend/fixtures/jobs.rb b/spec/frontend/fixtures/jobs.rb
index 64197a62301..22179c790bd 100644
--- a/spec/frontend/fixtures/jobs.rb
+++ b/spec/frontend/fixtures/jobs.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'builds-project') }
+ let(:user) { project.owner }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace_artifact, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) }
let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') }
@@ -22,28 +22,17 @@ RSpec.describe Projects::JobsController, '(JavaScript fixtures)', type: :control
render_views
before(:all) do
- clean_frontend_fixtures('builds/')
clean_frontend_fixtures('jobs/')
end
before do
- sign_in(admin)
+ sign_in(user)
end
after do
remove_repository(project)
end
- it 'builds/build-with-artifacts.html' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: build_with_artifacts.to_param
- }
-
- expect(response).to be_successful
- end
-
it 'jobs/delayed.json' do
get :show, params: {
namespace_id: project.namespace.to_param,
diff --git a/spec/frontend/fixtures/labels.rb b/spec/frontend/fixtures/labels.rb
index 2b7babb2e52..d7ca2aff18c 100644
--- a/spec/frontend/fixtures/labels.rb
+++ b/spec/frontend/fixtures/labels.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Labels (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
+ let(:user) { create(:user) }
let(:group) { create(:group, name: 'frontend-fixtures-group' )}
let(:project) { create(:project_empty_repo, namespace: group, path: 'labels-project') }
@@ -25,28 +25,10 @@ RSpec.describe 'Labels (JavaScript fixtures)' do
remove_repository(project)
end
- describe Groups::LabelsController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(admin)
- end
-
- it 'labels/group_labels.json' do
- get :index, params: {
- group_id: group
- }, format: 'json'
-
- expect(response).to be_successful
- end
- end
-
describe API::Helpers::LabelHelpers, type: :request do
include JavaScriptFixturesHelpers
include ApiHelpers
- let(:user) { create(:user) }
-
before do
group.add_owner(user)
end
@@ -62,7 +44,8 @@ RSpec.describe 'Labels (JavaScript fixtures)' do
render_views
before do
- sign_in(admin)
+ group.add_owner(user)
+ sign_in(user)
end
it 'labels/project_labels.json' do
diff --git a/spec/frontend/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb
index 6f281b26e6d..acce3891ada 100644
--- a/spec/frontend/fixtures/merge_requests.rb
+++ b/spec/frontend/fixtures/merge_requests.rb
@@ -5,15 +5,15 @@ require 'spec_helper'
RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
+ let(:user) { project.owner }
# rubocop: disable Layout/TrailingWhitespace
let(:description) do
<<~MARKDOWN.strip_heredoc
- [ ] Task List Item
- - [ ]
+ - [ ]
- [ ] Task List Item 2
MARKDOWN
end
@@ -55,7 +55,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
end
before do
- sign_in(admin)
+ sign_in(user)
allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
end
@@ -64,7 +64,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
end
it 'merge_requests/merge_request_of_current_user.html' do
- merge_request.update(author: admin)
+ merge_request.update(author: user)
render_merge_request(merge_request)
end
@@ -75,38 +75,20 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
render_merge_request(merge_request)
end
- it 'merge_requests/merged_merge_request.html' do
- expect_next_instance_of(MergeRequest) do |merge_request|
- allow(merge_request).to receive(:source_branch_exists?).and_return(true)
- allow(merge_request).to receive(:can_remove_source_branch?).and_return(true)
- end
- render_merge_request(merged_merge_request)
- end
-
it 'merge_requests/diff_comment.html' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+ create(:diff_note_on_merge_request, project: project, author: user, position: position, noteable: merge_request)
+ create(:note_on_merge_request, author: user, project: project, noteable: merge_request)
render_merge_request(merge_request)
end
- it 'merge_requests/merge_request_with_comment.html' do
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item')
- render_merge_request(merge_request)
- end
-
- it 'merge_requests/discussions.json' do
- create(:discussion_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- render_discussions_json(merge_request)
- end
-
it 'merge_requests/diff_discussion.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+ create(:diff_note_on_merge_request, project: project, author: user, position: position, noteable: merge_request)
render_discussions_json(merge_request)
end
it 'merge_requests/resolved_diff_discussion.json' do
- note = create(:discussion_note_on_merge_request, :resolved, project: project, author: admin, position: position, noteable: merge_request)
- create(:system_note, project: project, author: admin, noteable: merge_request, discussion_id: note.discussion.id)
+ note = create(:discussion_note_on_merge_request, :resolved, project: project, author: user, position: position, noteable: merge_request)
+ create(:system_note, project: project, author: user, noteable: merge_request, discussion_id: note.discussion.id)
render_discussions_json(merge_request)
end
@@ -129,7 +111,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
context 'with mentions' do
let(:group) { create(:group) }
- let(:description) { "@#{group.full_path} @all @#{admin.username}" }
+ let(:description) { "@#{group.full_path} @all @#{user.username}" }
it 'merge_requests/merge_request_with_mentions.html' do
render_merge_request(merge_request)
diff --git a/spec/frontend/fixtures/merge_requests_diffs.rb b/spec/frontend/fixtures/merge_requests_diffs.rb
index 63bd02d0fbd..6e07ef679f5 100644
--- a/spec/frontend/fixtures/merge_requests_diffs.rb
+++ b/spec/frontend/fixtures/merge_requests_diffs.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
+ let(:user) { project.owner }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
let(:path) { "files/ruby/popen.rb" }
let(:position) do
@@ -25,7 +25,7 @@ RSpec.describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)'
end
before do
- sign_in(admin)
+ sign_in(user)
end
after do
@@ -40,18 +40,6 @@ RSpec.describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)'
render_merge_request(merge_request, commit_id: project.commit.sha)
end
- it 'merge_request_diffs/inline_changes_tab_with_comments.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(merge_request)
- end
-
- it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(merge_request, view: 'parallel')
- end
-
private
def render_merge_request(merge_request, view: 'inline', **extra_params)
diff --git a/spec/frontend/fixtures/pipeline_schedules.rb b/spec/frontend/fixtures/pipeline_schedules.rb
index e47bb25ec0a..a7d43fdbe62 100644
--- a/spec/frontend/fixtures/pipeline_schedules.rb
+++ b/spec/frontend/fixtures/pipeline_schedules.rb
@@ -5,11 +5,11 @@ require 'spec_helper'
RSpec.describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :public, :repository) }
- let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: admin) }
- let!(:pipeline_schedule_populated) { create(:ci_pipeline_schedule, project: project, owner: admin) }
+ let(:user) { project.owner }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
+ let!(:pipeline_schedule_populated) { create(:ci_pipeline_schedule, project: project, owner: user) }
let!(:pipeline_schedule_variable1) { create(:ci_pipeline_schedule_variable, key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule_populated) }
let!(:pipeline_schedule_variable2) { create(:ci_pipeline_schedule_variable, key: 'bar', value: 'barvalue', pipeline_schedule: pipeline_schedule_populated) }
@@ -20,7 +20,7 @@ RSpec.describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', t
end
before do
- sign_in(admin)
+ sign_in(user)
end
it 'pipeline_schedules/edit.html' do
diff --git a/spec/frontend/fixtures/pipelines.rb b/spec/frontend/fixtures/pipelines.rb
index 93e2c19fc27..4270e38afcb 100644
--- a/spec/frontend/fixtures/pipelines.rb
+++ b/spec/frontend/fixtures/pipelines.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
RSpec.describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') }
let(:commit) { create(:commit, project: project) }
@@ -22,7 +21,7 @@ RSpec.describe Projects::PipelinesController, '(JavaScript fixtures)', type: :co
end
before do
- sign_in(admin)
+ sign_in(user)
end
it 'pipelines/pipelines.json' do
diff --git a/spec/frontend/fixtures/projects.rb b/spec/frontend/fixtures/projects.rb
index d0cedb0ef86..aa2f7dbed36 100644
--- a/spec/frontend/fixtures/projects.rb
+++ b/spec/frontend/fixtures/projects.rb
@@ -7,11 +7,11 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do
runners_token = 'runnerstoken:intabulasreferre'
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'builds-project', runners_token: runners_token) }
let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff') }
let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2', runners_token: runners_token) }
+ let(:user) { project.owner }
render_views
@@ -20,8 +20,8 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do
end
before do
- project.add_maintainer(admin)
- sign_in(admin)
+ project_with_repo.add_maintainer(user)
+ sign_in(user)
allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
end
@@ -30,15 +30,6 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do
end
describe ProjectsController, '(JavaScript fixtures)', type: :controller do
- it 'projects/dashboard.html' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- id: project
- }
-
- expect(response).to be_successful
- end
-
it 'projects/overview.html' do
get :show, params: {
namespace_id: project_with_repo.namespace.to_param,
diff --git a/spec/frontend/fixtures/prometheus_service.rb b/spec/frontend/fixtures/prometheus_service.rb
index 8c923d91d08..3a59ecf3868 100644
--- a/spec/frontend/fixtures/prometheus_service.rb
+++ b/spec/frontend/fixtures/prometheus_service.rb
@@ -5,10 +5,10 @@ require 'spec_helper'
RSpec.describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
let!(:service) { create(:prometheus_service, project: project) }
+ let(:user) { project.owner }
render_views
@@ -17,7 +17,7 @@ RSpec.describe Projects::ServicesController, '(JavaScript fixtures)', type: :con
end
before do
- sign_in(admin)
+ sign_in(user)
end
after do
diff --git a/spec/frontend/fixtures/raw.rb b/spec/frontend/fixtures/raw.rb
index 337067121d0..cf51f2389bc 100644
--- a/spec/frontend/fixtures/raw.rb
+++ b/spec/frontend/fixtures/raw.rb
@@ -10,19 +10,17 @@ RSpec.describe 'Raw files', '(JavaScript fixtures)' do
let(:response) { @blob.data.force_encoding('UTF-8') }
before(:all) do
- clean_frontend_fixtures('blob/balsamiq/')
clean_frontend_fixtures('blob/notebook/')
clean_frontend_fixtures('blob/pdf/')
+ clean_frontend_fixtures('blob/text/')
+ clean_frontend_fixtures('blob/binary/')
+ clean_frontend_fixtures('blob/images/')
end
after do
remove_repository(project)
end
- it 'blob/balsamiq/test.bmpr' do
- @blob = project.repository.blob_at('b89b56d79', 'files/images/balsamiq.bmpr')
- end
-
it 'blob/notebook/basic.json' do
@blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb')
end
@@ -38,4 +36,16 @@ RSpec.describe 'Raw files', '(JavaScript fixtures)' do
it 'blob/pdf/test.pdf' do
@blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf')
end
+
+ it 'blob/text/README.md' do
+ @blob = project.repository.blob_at('e774ebd33', 'README.md')
+ end
+
+ it 'blob/images/logo-white.png' do
+ @blob = project.repository.blob_at('e774ebd33', 'files/images/logo-white.png')
+ end
+
+ it 'blob/binary/Gemfile.zip' do
+ @blob = project.repository.blob_at('e774ebd33', 'Gemfile.zip')
+ end
end
diff --git a/spec/frontend/fixtures/search.rb b/spec/frontend/fixtures/search.rb
index 7819d0774a7..264ce7d010c 100644
--- a/spec/frontend/fixtures/search.rb
+++ b/spec/frontend/fixtures/search.rb
@@ -7,7 +7,7 @@ RSpec.describe SearchController, '(JavaScript fixtures)', type: :controller do
render_views
- let_it_be(:user) { create(:admin) }
+ let_it_be(:user) { create(:user) }
before(:all) do
clean_frontend_fixtures('search/')
@@ -66,9 +66,13 @@ RSpec.describe SearchController, '(JavaScript fixtures)', type: :controller do
offset: 0)
end
+ before do
+ project.add_developer(user)
+ end
+
it 'search/blob_search_result.html' do
- expect_next_instance_of(SearchService) do |search_service|
- expect(search_service).to receive(:search_objects).and_return(blobs)
+ allow_next_instance_of(SearchServicePresenter) do |search_service|
+ allow(search_service).to receive(:search_objects).and_return(blobs)
end
get :show, params: {
diff --git a/spec/frontend/fixtures/services.rb b/spec/frontend/fixtures/services.rb
index 43230301296..7472af802f3 100644
--- a/spec/frontend/fixtures/services.rb
+++ b/spec/frontend/fixtures/services.rb
@@ -5,10 +5,10 @@ require 'spec_helper'
RSpec.describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
let!(:service) { create(:custom_issue_tracker_service, project: project) }
+ let(:user) { project.owner }
render_views
@@ -17,7 +17,7 @@ RSpec.describe Projects::ServicesController, '(JavaScript fixtures)', type: :con
end
before do
- sign_in(admin)
+ sign_in(user)
end
after do
diff --git a/spec/frontend/fixtures/snippet.rb b/spec/frontend/fixtures/snippet.rb
index 2e67a2ecfe3..5211d52f374 100644
--- a/spec/frontend/fixtures/snippet.rb
+++ b/spec/frontend/fixtures/snippet.rb
@@ -5,10 +5,10 @@ require 'spec_helper'
RSpec.describe SnippetsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
- let(:snippet) { create(:personal_snippet, :public, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: admin) }
+ let(:user) { project.owner }
+ let(:snippet) { create(:personal_snippet, :public, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: user) }
render_views
@@ -17,7 +17,7 @@ RSpec.describe SnippetsController, '(JavaScript fixtures)', type: :controller do
end
before do
- sign_in(admin)
+ sign_in(user)
allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
end
@@ -26,7 +26,7 @@ RSpec.describe SnippetsController, '(JavaScript fixtures)', type: :controller do
end
it 'snippets/show.html' do
- create(:discussion_note_on_project_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item')
+ create(:discussion_note_on_project_snippet, noteable: snippet, project: project, author: user, note: '- [ ] Task List Item')
get(:show, params: { id: snippet.to_param })
diff --git a/spec/frontend/fixtures/static/balsamiq_viewer.html b/spec/frontend/fixtures/static/balsamiq_viewer.html
deleted file mode 100644
index cdd723d1a84..00000000000
--- a/spec/frontend/fixtures/static/balsamiq_viewer.html
+++ /dev/null
@@ -1 +0,0 @@
-<div class="file-content balsamiq-viewer" data-endpoint="/test" id="js-balsamiq-viewer"></div>
diff --git a/spec/frontend/fixtures/static/create_item_dropdown.html b/spec/frontend/fixtures/static/create_item_dropdown.html
index d2d38370092..aac7d3397ce 100644
--- a/spec/frontend/fixtures/static/create_item_dropdown.html
+++ b/spec/frontend/fixtures/static/create_item_dropdown.html
@@ -1,11 +1,42 @@
<div class="js-create-item-dropdown-fixture-root">
-<input name="variable[environment]" type="hidden">
-<div class="dropdown "><button class="dropdown-menu-toggle js-dropdown-menu-toggle" type="button" data-toggle="dropdown"><span class="dropdown-toggle-text ">some label</span><i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i></button><div class="dropdown-menu dropdown-select dropdown-menu-selectable"><div class="dropdown-input"><input type="search" id="" class="dropdown-input-field" autocomplete="off" /><i aria-hidden="true" data-hidden="true" class="fa fa-search dropdown-input-search"></i><i aria-hidden="true" data-hidden="true" role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i></div><div class="dropdown-content js-dropdown-content"></div><div class="dropdown-footer"><ul class="dropdown-footer-list">
-<li>
-<button class="dropdown-create-new-item-button js-dropdown-create-new-item">
-Create wildcard
-<code></code>
-</button>
-</li>
-</ul>
-</div><div class="dropdown-loading"><i aria-hidden="true" data-hidden="true" class="fa fa-spinner fa-spin"></i></div></div></div></div>
+ <input name="variable[environment]" type="hidden" />
+ <div class="dropdown ">
+ <button
+ class="dropdown-menu-toggle js-dropdown-menu-toggle"
+ type="button"
+ data-toggle="dropdown"
+ >
+ <span class="dropdown-toggle-text ">some label</span
+ ><i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i>
+ </button>
+ <div class="dropdown-menu dropdown-select dropdown-menu-selectable">
+ <div class="dropdown-input">
+ <input type="search" id="" class="dropdown-input-field" autocomplete="off" /><i
+ aria-hidden="true"
+ data-hidden="true"
+ class="fa fa-search dropdown-input-search"
+ ></i
+ ><i
+ aria-hidden="true"
+ data-hidden="true"
+ role="button"
+ class="fa fa-times dropdown-input-clear js-dropdown-input-clear"
+ ></i>
+ </div>
+ <div class="dropdown-content js-dropdown-content"></div>
+ <div class="dropdown-footer">
+ <ul class="dropdown-footer-list">
+ <li>
+ <button class="dropdown-create-new-item-button js-dropdown-create-new-item">
+ Create wildcard
+ <code></code>
+ </button>
+ </li>
+ </ul>
+ </div>
+ <div class="dropdown-loading">
+ <span aria-hidden="true" data-hidden="true" class="gl-spinner"></span>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/spec/frontend/fixtures/static/deprecated_jquery_dropdown.html b/spec/frontend/fixtures/static/deprecated_jquery_dropdown.html
index 3db9bafcb9f..41e7170b5c6 100644
--- a/spec/frontend/fixtures/static/deprecated_jquery_dropdown.html
+++ b/spec/frontend/fixtures/static/deprecated_jquery_dropdown.html
@@ -32,7 +32,7 @@
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading">
- <i class="fa fa-spinner fa-spin"></i>
+ <span class="gl-spinner"></span>
</div>
</div>
</div>
diff --git a/spec/frontend/fixtures/static/environments/table.html b/spec/frontend/fixtures/static/environments/table.html
deleted file mode 100644
index 417af564ff1..00000000000
--- a/spec/frontend/fixtures/static/environments/table.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<table>
-<thead>
-<tr>
-<th>Environment</th>
-<th>Last deployment</th>
-<th>Job</th>
-<th>Commit</th>
-<th></th>
-<th></th>
-</tr>
-</thead>
-<tbody>
-<tr id="environment-row"></tr>
-</tbody>
-</table>
diff --git a/spec/frontend/fixtures/static/issuable_filter.html b/spec/frontend/fixtures/static/issuable_filter.html
deleted file mode 100644
index 06b70fb43f1..00000000000
--- a/spec/frontend/fixtures/static/issuable_filter.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<form action="/user/project/issues?scope=all&amp;state=closed" class="js-filter-form">
-<input id="utf8" name="utf8" value="✓">
-<input id="check-all-issues" name="check-all-issues">
-<input id="search" name="search">
-<input id="author_id" name="author_id">
-<input id="assignee_id" name="assignee_id">
-<input id="milestone_title" name="milestone_title">
-<input id="label_name" name="label_name">
-</form>
diff --git a/spec/frontend/fixtures/static/issue_with_mermaid_graph.html b/spec/frontend/fixtures/static/issue_with_mermaid_graph.html
deleted file mode 100644
index e9fa75c8ba9..00000000000
--- a/spec/frontend/fixtures/static/issue_with_mermaid_graph.html
+++ /dev/null
@@ -1,82 +0,0 @@
-<div class="description" updated-at="">
- <div class="md issue-realtime-trigger-pulse">
- <svg
- id="mermaid-1587752414912"
- width="100%"
- xmlns="http://www.w3.org/2000/svg"
- style="max-width: 185.35000610351562px;"
- viewBox="0 0 185.35000610351562 50.5"
- class="mermaid"
- >
- <g transform="translate(0, 0)">
- <g class="output">
- <g class="clusters"></g>
- <g class="edgePaths"></g>
- <g class="edgeLabels"></g>
- <g class="nodes">
- <g
- class="node js-issuable-buttons btn-close clickable"
- style="opacity: 1;"
- id="A"
- transform="translate(92.67500305175781,25.25)"
- title="click to PUT"
- >
- <a
- class="js-issuable-buttons btn-close clickable"
- href="https://invalid"
- rel="noopener"
- >
- <rect
- rx="0"
- ry="0"
- x="-84.67500305175781"
- y="-17.25"
- width="169.35000610351562"
- height="34.5"
- class="label-container"
- ></rect>
- <g class="label" transform="translate(0,0)">
- <g transform="translate(-74.67500305175781,-7.25)">
- <text style="">
- <tspan xml:space="preserve" dy="1em" x="1">Click to send a PUT request</tspan>
- </text>
- </g>
- </g>
- </a>
- </g>
- </g>
- </g>
- </g>
- <text class="source" display="none">
- Click to send a PUT request
- </text>
- </svg>
- </div>
- <textarea
- data-update-url="/h5bp/html5-boilerplate/-/issues/35.json"
- dir="auto"
- class="hidden js-task-list-field"
- ></textarea>
- <div class="modal-open recaptcha-modal js-recaptcha-modal" style="display: none;">
- <div role="dialog" tabindex="-1" class="modal d-block">
- <div role="document" class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <h4 class="modal-title float-left">Please solve the reCAPTCHA</h4>
- <button type="button" data-dismiss="modal" aria-label="Close" class="close float-right">
- <span aria-hidden="true">×</span>
- </button>
- </div>
- <div class="modal-body">
- <div>
- <p>We want to be sure it is you, please confirm you are not a robot.</p>
- <div></div>
- </div>
- </div>
- <!---->
- </div>
- </div>
- </div>
- <div class="modal-backdrop fade show"></div>
- </div>
-</div>
diff --git a/spec/frontend/fixtures/static/merge_requests_show.html b/spec/frontend/fixtures/static/merge_requests_show.html
deleted file mode 100644
index 87e36c9f315..00000000000
--- a/spec/frontend/fixtures/static/merge_requests_show.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<a class="btn-close"></a>
-<div class="detail-page-description">
-<div class="description js-task-list-container">
-<div class="md">
-<ul class="task-list">
-<li class="task-list-item">
-<input class="task-list-item-checkbox" type="checkbox">
-Task List Item
-</li>
-</ul>
-<textarea class="js-task-list-field">- [ ] Task List Item</textarea>
-</div>
-</div>
-</div>
-<form action="/foo" class="js-issuable-update"></form>
diff --git a/spec/frontend/fixtures/static/mini_dropdown_graph.html b/spec/frontend/fixtures/static/mini_dropdown_graph.html
index cb55698b709..cde811d4f52 100644
--- a/spec/frontend/fixtures/static/mini_dropdown_graph.html
+++ b/spec/frontend/fixtures/static/mini_dropdown_graph.html
@@ -7,7 +7,7 @@
<ul></ul>
</li>
<li class="js-builds-dropdown-loading hidden">
- <span class="fa fa-spinner"></span>
+ <span class="gl-spinner"></span>
</li>
</ul>
</div>
diff --git a/spec/frontend/fixtures/static/pipelines.html b/spec/frontend/fixtures/static/pipelines.html
deleted file mode 100644
index 42333f94f2f..00000000000
--- a/spec/frontend/fixtures/static/pipelines.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<div>
-<div data-can-create-pipeline="true" data-ci-lint-path="foo" data-empty-state-svg-path="foo" data-endpoint="foo" data-error-state-svg-path="foo" data-has-ci="foo" data-help-auto-devops-path="foo" data-help-page-path="foo" data-new-pipeline-path="foo" data-reset-cache-path="foo" id="pipelines-list-vue"></div>
-</div>
diff --git a/spec/frontend/fixtures/static/project_select_combo_button.html b/spec/frontend/fixtures/static/project_select_combo_button.html
index 50c826051c0..f13f9075706 100644
--- a/spec/frontend/fixtures/static/project_select_combo_button.html
+++ b/spec/frontend/fixtures/static/project_select_combo_button.html
@@ -1,9 +1,9 @@
<div class="project-item-select-holder">
-<input class="project-item-select" data-group-id="12345" data-relative-path="issues/new">
-<a class="new-project-item-link" data-label="New issue" data-type="issues" href="">
-<i class="fa fa-spinner spin"></i>
-</a>
-<a class="new-project-item-select-button">
-<i class="fa fa-caret-down"></i>
-</a>
+ <input class="project-item-select" data-group-id="12345" data-relative-path="issues/new" />
+ <a class="new-project-item-link" data-label="New issue" data-type="issues" href="">
+ <span class="gl-spinner"></span>
+ </a>
+ <a class="new-project-item-select-button">
+ <i class="fa fa-caret-down"></i>
+ </a>
</div>
diff --git a/spec/frontend/fixtures/static/whats_new_notification.html b/spec/frontend/fixtures/static/whats_new_notification.html
new file mode 100644
index 00000000000..30d5eea91cc
--- /dev/null
+++ b/spec/frontend/fixtures/static/whats_new_notification.html
@@ -0,0 +1,6 @@
+<div class='whats-new-notification-fixture-root'>
+ <div class='app' data-storage-key='storage-key'></div>
+ <div class='header-help'>
+ <div class='js-whats-new-notification-count'></div>
+ </div>
+</div>
diff --git a/spec/frontend/fixtures/tags.rb b/spec/frontend/fixtures/tags.rb
index b2a5429fac8..9483f0a4492 100644
--- a/spec/frontend/fixtures/tags.rb
+++ b/spec/frontend/fixtures/tags.rb
@@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe 'Tags (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- let_it_be(:admin) { create(:admin) }
let_it_be(:project) { create(:project, :repository, path: 'tags-project') }
+ let_it_be(:user) { project.owner }
before(:all) do
clean_frontend_fixtures('api/tags/')
@@ -20,7 +20,7 @@ RSpec.describe 'Tags (JavaScript fixtures)' do
include ApiHelpers
it 'api/tags/tags.json' do
- get api("/projects/#{project.id}/repository/tags", admin)
+ get api("/projects/#{project.id}/repository/tags", user)
expect(response).to be_successful
end
diff --git a/spec/frontend/fixtures/todos.rb b/spec/frontend/fixtures/todos.rb
index 399be272e9b..985afafe50e 100644
--- a/spec/frontend/fixtures/todos.rb
+++ b/spec/frontend/fixtures/todos.rb
@@ -5,13 +5,13 @@ require 'spec_helper'
RSpec.describe 'Todos (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
+ let(:user) { project.owner }
let(:issue_1) { create(:issue, title: 'issue_1', project: project) }
- let!(:todo_1) { create(:todo, user: admin, project: project, target: issue_1, created_at: 5.hours.ago) }
+ let!(:todo_1) { create(:todo, user: user, project: project, target: issue_1, created_at: 5.hours.ago) }
let(:issue_2) { create(:issue, title: 'issue_2', project: project) }
- let!(:todo_2) { create(:todo, :done, user: admin, project: project, target: issue_2, created_at: 50.hours.ago) }
+ let!(:todo_2) { create(:todo, :done, user: user, project: project, target: issue_2, created_at: 50.hours.ago) }
before(:all) do
clean_frontend_fixtures('todos/')
@@ -25,7 +25,7 @@ RSpec.describe 'Todos (JavaScript fixtures)' do
render_views
before do
- sign_in(admin)
+ sign_in(user)
end
it 'todos/todos.html' do
@@ -39,7 +39,7 @@ RSpec.describe 'Todos (JavaScript fixtures)' do
render_views
before do
- sign_in(admin)
+ sign_in(user)
end
it 'todos/todos.json' do
diff --git a/spec/frontend/frequent_items/components/app_spec.js b/spec/frontend/frequent_items/components/app_spec.js
index b4f36b82385..439a410eaa1 100644
--- a/spec/frontend/frequent_items/components/app_spec.js
+++ b/spec/frontend/frequent_items/components/app_spec.js
@@ -6,10 +6,10 @@ import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import appComponent from '~/frequent_items/components/app.vue';
import eventHub from '~/frequent_items/event_hub';
-import store from '~/frequent_items/store';
import { FREQUENT_ITEMS, HOUR_IN_MS } from '~/frequent_items/constants';
import { getTopFrequentItems } from '~/frequent_items/utils';
import { currentSession, mockFrequentProjects, mockSearchedProjects } from '../mock_data';
+import { createStore } from '~/frequent_items/store';
useLocalStorageSpy();
@@ -18,6 +18,7 @@ const createComponentWithStore = (namespace = 'projects') => {
session = currentSession[namespace];
gon.api_version = session.apiVersion;
const Component = Vue.extend(appComponent);
+ const store = createStore();
return mountComponentWithStore(Component, {
store,
diff --git a/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js b/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js
index ab5784b8f7a..1160ed5c84b 100644
--- a/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js
@@ -1,10 +1,14 @@
import { shallowMount } from '@vue/test-utils';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { trimText } from 'helpers/text_helper';
import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue';
-import { mockProject } from '../mock_data'; // can also use 'mockGroup', but not useful to test here
+import { createStore } from '~/frequent_items/store';
+import { mockProject } from '../mock_data';
describe('FrequentItemsListItemComponent', () => {
let wrapper;
+ let trackingSpy;
+ let store = createStore();
const findTitle = () => wrapper.find({ ref: 'frequentItemsItemTitle' });
const findAvatar = () => wrapper.find({ ref: 'frequentItemsItemAvatar' });
@@ -18,6 +22,7 @@ describe('FrequentItemsListItemComponent', () => {
const createComponent = (props = {}) => {
wrapper = shallowMount(frequentItemsListItemComponent, {
+ store,
propsData: {
itemId: mockProject.id,
itemName: mockProject.name,
@@ -29,7 +34,14 @@ describe('FrequentItemsListItemComponent', () => {
});
};
+ beforeEach(() => {
+ store = createStore({ dropdownType: 'project' });
+ trackingSpy = mockTracking('_category_', document, jest.spyOn);
+ trackingSpy.mockImplementation(() => {});
+ });
+
afterEach(() => {
+ unmockTracking();
wrapper.destroy();
wrapper = null;
});
@@ -97,5 +109,18 @@ describe('FrequentItemsListItemComponent', () => {
`('should render $expected $name', ({ selector, expected }) => {
expect(selector()).toHaveLength(expected);
});
+
+ it('tracks when item link is clicked', () => {
+ const link = wrapper.find('a');
+ // NOTE: this listener is required to prevent the click from going through and causing:
+ // `Error: Not implemented: navigation ...`
+ link.element.addEventListener('click', e => {
+ e.preventDefault();
+ });
+ link.trigger('click');
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_link', {
+ label: 'project_dropdown_frequent_items_list_item',
+ });
+ });
});
});
diff --git a/spec/frontend/frequent_items/components/frequent_items_list_spec.js b/spec/frontend/frequent_items/components/frequent_items_list_spec.js
index 238fd508053..96f73ab1468 100644
--- a/spec/frontend/frequent_items/components/frequent_items_list_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_list_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { createStore } from '~/frequent_items/store';
import frequentItemsListComponent from '~/frequent_items/components/frequent_items_list.vue';
import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue';
import { mockFrequentProjects } from '../mock_data';
@@ -8,6 +9,7 @@ describe('FrequentItemsListComponent', () => {
const createComponent = (props = {}) => {
wrapper = mount(frequentItemsListComponent, {
+ store: createStore(),
propsData: {
namespace: 'projects',
items: mockFrequentProjects,
@@ -25,7 +27,7 @@ describe('FrequentItemsListComponent', () => {
describe('computed', () => {
describe('isListEmpty', () => {
- it('should return `true` or `false` representing whether if `items` is empty or not with projects', () => {
+ it('should return `true` or `false` representing whether if `items` is empty or not with projects', async () => {
createComponent({
items: [],
});
@@ -35,13 +37,14 @@ describe('FrequentItemsListComponent', () => {
wrapper.setProps({
items: mockFrequentProjects,
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.isListEmpty).toBe(false);
});
});
describe('fetched item messages', () => {
- it('should return appropriate empty list message based on value of `localStorageFailed` prop with projects', () => {
+ it('should return appropriate empty list message based on value of `localStorageFailed` prop with projects', async () => {
createComponent({
isFetchFailed: true,
});
@@ -53,13 +56,14 @@ describe('FrequentItemsListComponent', () => {
wrapper.setProps({
isFetchFailed: false,
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.listEmptyMessage).toBe('Projects you visit often will appear here');
});
});
describe('searched item messages', () => {
- it('should return appropriate empty list message based on value of `searchFailed` prop with projects', () => {
+ it('should return appropriate empty list message based on value of `searchFailed` prop with projects', async () => {
createComponent({
hasSearchQuery: true,
isFetchFailed: true,
@@ -70,6 +74,7 @@ describe('FrequentItemsListComponent', () => {
wrapper.setProps({
isFetchFailed: false,
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.listEmptyMessage).toBe('Sorry, no projects matched your search');
});
diff --git a/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js b/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js
index c5155315bb9..f5e654e6bcb 100644
--- a/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js
@@ -1,23 +1,35 @@
import { shallowMount } from '@vue/test-utils';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import searchComponent from '~/frequent_items/components/frequent_items_search_input.vue';
+import { createStore } from '~/frequent_items/store';
import eventHub from '~/frequent_items/event_hub';
-const createComponent = (namespace = 'projects') =>
- shallowMount(searchComponent, {
- propsData: { namespace },
- });
-
describe('FrequentItemsSearchInputComponent', () => {
let wrapper;
+ let trackingSpy;
let vm;
+ let store;
+
+ const createComponent = (namespace = 'projects') =>
+ shallowMount(searchComponent, {
+ store,
+ propsData: { namespace },
+ });
beforeEach(() => {
+ store = createStore({ dropdownType: 'project' });
+ jest.spyOn(store, 'dispatch').mockImplementation(() => {});
+
+ trackingSpy = mockTracking('_category_', document, jest.spyOn);
+ trackingSpy.mockImplementation(() => {});
+
wrapper = createComponent();
({ vm } = wrapper);
});
afterEach(() => {
+ unmockTracking();
vm.$destroy();
});
@@ -76,4 +88,24 @@ describe('FrequentItemsSearchInputComponent', () => {
);
});
});
+
+ describe('tracking', () => {
+ it('tracks when search query is entered', async () => {
+ expect(trackingSpy).not.toHaveBeenCalled();
+ expect(store.dispatch).not.toHaveBeenCalled();
+
+ const value = 'my project';
+
+ const input = wrapper.find('input');
+ input.setValue(value);
+ input.trigger('input');
+
+ await wrapper.vm.$nextTick();
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'type_search_query', {
+ label: 'project_dropdown_frequent_items_search_input',
+ });
+ expect(store.dispatch).toHaveBeenCalledWith('setSearchQuery', value);
+ });
+ });
});
diff --git a/spec/frontend/frequent_items/mock_data.js b/spec/frontend/frequent_items/mock_data.js
index 8c3c66f67ff..5e15b4b33e0 100644
--- a/spec/frontend/frequent_items/mock_data.js
+++ b/spec/frontend/frequent_items/mock_data.js
@@ -30,7 +30,6 @@ export const currentSession = {
};
export const mockNamespace = 'projects';
-
export const mockStorageKey = 'test-user/frequent-projects';
export const mockGroup = {
diff --git a/spec/frontend/graphql_shared/utils_spec.js b/spec/frontend/graphql_shared/utils_spec.js
index 6a630195126..d392b0f0575 100644
--- a/spec/frontend/graphql_shared/utils_spec.js
+++ b/spec/frontend/graphql_shared/utils_spec.js
@@ -1,4 +1,12 @@
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import {
+ getIdFromGraphQLId,
+ convertToGraphQLId,
+ convertToGraphQLIds,
+} from '~/graphql_shared/utils';
+
+const mockType = 'Group';
+const mockId = 12;
+const mockGid = `gid://gitlab/Group/12`;
describe('getIdFromGraphQLId', () => {
[
@@ -44,3 +52,32 @@ describe('getIdFromGraphQLId', () => {
});
});
});
+
+describe('convertToGraphQLId', () => {
+ it('combines $type and $id into $result', () => {
+ expect(convertToGraphQLId(mockType, mockId)).toBe(mockGid);
+ });
+
+ it.each`
+ type | id | message
+ ${mockType} | ${null} | ${'id must be a number or string; got object'}
+ ${null} | ${mockId} | ${'type must be a string; got object'}
+ `('throws TypeError with "$message" if a param is missing', ({ type, id, message }) => {
+ expect(() => convertToGraphQLId(type, id)).toThrow(new TypeError(message));
+ });
+});
+
+describe('convertToGraphQLIds', () => {
+ it('combines $type and $id into $result', () => {
+ expect(convertToGraphQLIds(mockType, [mockId])).toStrictEqual([mockGid]);
+ });
+
+ it.each`
+ type | ids | message
+ ${mockType} | ${null} | ${"Cannot read property 'map' of null"}
+ ${mockType} | ${[mockId, null]} | ${'id must be a number or string; got object'}
+ ${null} | ${[mockId]} | ${'type must be a string; got object'}
+ `('throws TypeError with "$message" if a param is missing', ({ type, ids, message }) => {
+ expect(() => convertToGraphQLIds(type, ids)).toThrow(new TypeError(message));
+ });
+});
diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js
index 691f8896d74..72d8e23f28b 100644
--- a/spec/frontend/groups/components/app_spec.js
+++ b/spec/frontend/groups/components/app_spec.js
@@ -35,7 +35,7 @@ describe('AppComponent', () => {
let mock;
let getGroupsSpy;
- const store = new GroupsStore(false);
+ const store = new GroupsStore({ hideProjects: false });
const service = new GroupsService(mockEndpoint);
const createShallowComponent = (hideProjects = false) => {
diff --git a/spec/frontend/groups/components/group_item_spec.js b/spec/frontend/groups/components/group_item_spec.js
index 83acbb152b5..32bae812c86 100644
--- a/spec/frontend/groups/components/group_item_spec.js
+++ b/spec/frontend/groups/components/group_item_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import groupItemComponent from '~/groups/components/group_item.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
+import { getGroupItemMicrodata } from '~/groups/store/utils';
import eventHub from '~/groups/event_hub';
import * as urlUtilities from '~/lib/utils/url_utility';
import { mockParentGroupItem, mockChildren } from '../mock_data';
@@ -30,6 +31,11 @@ describe('GroupItemComponent', () => {
vm.$destroy();
});
+ const withMicrodata = group => ({
+ ...group,
+ microdata: getGroupItemMicrodata(group),
+ });
+
describe('computed', () => {
describe('groupDomId', () => {
it('should return ID string suffixed with group ID', () => {
@@ -212,4 +218,47 @@ describe('GroupItemComponent', () => {
expect(vm.$el.querySelector('.group-list-tree')).toBeDefined();
});
});
+ describe('schema.org props', () => {
+ describe('when showSchemaMarkup is disabled on the group', () => {
+ it.each(['itemprop', 'itemtype', 'itemscope'], 'it does not set %s', attr => {
+ expect(vm.$el.getAttribute(attr)).toBeNull();
+ });
+ it.each(
+ ['.js-group-avatar', '.js-group-name', '.js-group-description'],
+ 'it does not set `itemprop` on sub-nodes',
+ selector => {
+ expect(vm.$el.querySelector(selector).getAttribute('itemprop')).toBeNull();
+ },
+ );
+ });
+ describe('when group has microdata', () => {
+ beforeEach(() => {
+ const group = withMicrodata({
+ ...mockParentGroupItem,
+ avatarUrl: 'http://foo.bar',
+ description: 'Foo Bar',
+ });
+
+ vm = createComponent(group);
+ });
+
+ it.each`
+ attr | value
+ ${'itemscope'} | ${'itemscope'}
+ ${'itemtype'} | ${'https://schema.org/Organization'}
+ ${'itemprop'} | ${'subOrganization'}
+ `('it does set correct $attr', ({ attr, value } = {}) => {
+ expect(vm.$el.getAttribute(attr)).toBe(value);
+ });
+
+ it.each`
+ selector | propValue
+ ${'[data-testid="group-avatar"]'} | ${'logo'}
+ ${'[data-testid="group-name"]'} | ${'name'}
+ ${'[data-testid="group-description"]'} | ${'description'}
+ `('it does set correct $selector', ({ selector, propValue } = {}) => {
+ expect(vm.$el.querySelector(selector).getAttribute('itemprop')).toBe(propValue);
+ });
+ });
+ });
});
diff --git a/spec/frontend/groups/components/visibility_level_dropdown_spec.js b/spec/frontend/groups/components/visibility_level_dropdown_spec.js
new file mode 100644
index 00000000000..bf9508dc768
--- /dev/null
+++ b/spec/frontend/groups/components/visibility_level_dropdown_spec.js
@@ -0,0 +1,73 @@
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Component from '~/groups/components/visibility_level_dropdown.vue';
+
+describe('Visibility Level Dropdown', () => {
+ let wrapper;
+
+ const options = [
+ { level: 0, label: 'Private', description: 'Private description' },
+ { level: 20, label: 'Public', description: 'Public description' },
+ ];
+ const defaultLevel = 0;
+
+ const createComponent = propsData => {
+ wrapper = shallowMount(Component, {
+ propsData,
+ });
+ };
+
+ beforeEach(() => {
+ createComponent({
+ visibilityLevelOptions: options,
+ defaultLevel,
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const hiddenInputValue = () =>
+ wrapper.find("input[name='group[visibility_level]']").attributes('value');
+ const dropdownText = () => wrapper.find(GlDropdown).props('text');
+ const findDropdownItems = () =>
+ wrapper.findAll(GlDropdownItem).wrappers.map(option => ({
+ text: option.text(),
+ secondaryText: option.props('secondaryText'),
+ }));
+
+ describe('Default values', () => {
+ it('sets the value of the hidden input to the default value', () => {
+ expect(hiddenInputValue()).toBe(options[0].level.toString());
+ });
+
+ it('sets the text of the dropdown to the default value', () => {
+ expect(dropdownText()).toBe(options[0].label);
+ });
+
+ it('shows all dropdown options', () => {
+ expect(findDropdownItems()).toEqual(
+ options.map(({ label, description }) => ({ text: label, secondaryText: description })),
+ );
+ });
+ });
+
+ describe('Selecting an option', () => {
+ beforeEach(() => {
+ wrapper
+ .findAll(GlDropdownItem)
+ .at(1)
+ .vm.$emit('click');
+ });
+
+ it('sets the value of the hidden input to the selected value', () => {
+ expect(hiddenInputValue()).toBe(options[1].level.toString());
+ });
+
+ it('sets the text of the dropdown to the selected value', () => {
+ expect(dropdownText()).toBe(options[1].label);
+ });
+ });
+});
diff --git a/spec/frontend/groups/members/components/app_spec.js b/spec/frontend/groups/members/components/app_spec.js
index de9f30649e9..208e2fc35b6 100644
--- a/spec/frontend/groups/members/components/app_spec.js
+++ b/spec/frontend/groups/members/components/app_spec.js
@@ -3,12 +3,10 @@ import { nextTick } from 'vue';
import Vuex from 'vuex';
import { GlAlert } from '@gitlab/ui';
import App from '~/groups/members/components/app.vue';
+import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import * as commonUtils from '~/lib/utils/common_utils';
-import {
- RECEIVE_MEMBER_ROLE_ERROR,
- HIDE_ERROR,
-} from '~/vuex_shared/modules/members/mutation_types';
-import mutations from '~/vuex_shared/modules/members/mutations';
+import { RECEIVE_MEMBER_ROLE_ERROR, HIDE_ERROR } from '~/members/store/mutation_types';
+import mutations from '~/members/store/mutations';
describe('GroupMembersApp', () => {
const localVue = createLocalVue();
@@ -17,7 +15,7 @@ describe('GroupMembersApp', () => {
let wrapper;
let store;
- const createComponent = (state = {}) => {
+ const createComponent = (state = {}, options = {}) => {
store = new Vuex.Store({
state: {
showError: true,
@@ -30,10 +28,12 @@ describe('GroupMembersApp', () => {
wrapper = shallowMount(App, {
localVue,
store,
+ ...options,
});
};
const findAlert = () => wrapper.find(GlAlert);
+ const findFilterSortContainer = () => wrapper.find(FilterSortContainer);
beforeEach(() => {
commonUtils.scrollToElement = jest.fn();
@@ -86,4 +86,22 @@ describe('GroupMembersApp', () => {
expect(findAlert().exists()).toBe(false);
});
});
+
+ describe.each`
+ featureFlagValue | exists
+ ${true} | ${true}
+ ${false} | ${false}
+ `(
+ 'when `group_members_filtered_search` feature flag is $featureFlagValue',
+ ({ featureFlagValue, exists }) => {
+ it(`${exists ? 'renders' : 'does not render'} FilterSortContainer`, () => {
+ createComponent(
+ {},
+ { provide: { glFeatures: { groupMembersFilteredSearch: featureFlagValue } } },
+ );
+
+ expect(findFilterSortContainer().exists()).toBe(exists);
+ });
+ },
+ );
});
diff --git a/spec/frontend/groups/members/index_spec.js b/spec/frontend/groups/members/index_spec.js
index aaa36665c45..5c717e53229 100644
--- a/spec/frontend/groups/members/index_spec.js
+++ b/spec/frontend/groups/members/index_spec.js
@@ -9,12 +9,13 @@ describe('initGroupMembersApp', () => {
let wrapper;
const setup = () => {
- vm = initGroupMembersApp(
- el,
- ['account'],
- { table: { 'data-qa-selector': 'members_list' } },
- () => ({}),
- );
+ vm = initGroupMembersApp(el, {
+ tableFields: ['account'],
+ tableAttrs: { table: { 'data-qa-selector': 'members_list' } },
+ tableSortableFields: ['account'],
+ requestFormatter: () => ({}),
+ filteredSearchBar: { show: false },
+ });
wrapper = createWrapper(vm);
};
@@ -22,6 +23,7 @@ describe('initGroupMembersApp', () => {
el = document.createElement('div');
el.setAttribute('data-members', membersJsonString);
el.setAttribute('data-group-id', '234');
+ el.setAttribute('data-can-manage-members', 'true');
el.setAttribute('data-member-path', '/groups/foo-bar/-/group_members/:id');
window.gon = { current_user_id: 123 };
@@ -61,6 +63,12 @@ describe('initGroupMembersApp', () => {
expect(vm.$store.state.sourceId).toBe(234);
});
+ it('parses and sets `data-can-manage-members` as `canManageMembers` in Vuex store', () => {
+ setup();
+
+ expect(vm.$store.state.canManageMembers).toBe(true);
+ });
+
it('parses and sets `members` in Vuex store', () => {
setup();
@@ -79,12 +87,24 @@ describe('initGroupMembersApp', () => {
expect(vm.$store.state.tableAttrs).toEqual({ table: { 'data-qa-selector': 'members_list' } });
});
+ it('sets `tableSortableFields` in Vuex store', () => {
+ setup();
+
+ expect(vm.$store.state.tableSortableFields).toEqual(['account']);
+ });
+
it('sets `requestFormatter` in Vuex store', () => {
setup();
expect(vm.$store.state.requestFormatter()).toEqual({});
});
+ it('sets `filteredSearchBar` in Vuex store', () => {
+ setup();
+
+ expect(vm.$store.state.filteredSearchBar).toEqual({ show: false });
+ });
+
it('sets `memberPath` in Vuex store', () => {
setup();
diff --git a/spec/frontend/groups/members/utils_spec.js b/spec/frontend/groups/members/utils_spec.js
index b0921c7642f..68945174e9d 100644
--- a/spec/frontend/groups/members/utils_spec.js
+++ b/spec/frontend/groups/members/utils_spec.js
@@ -13,6 +13,7 @@ describe('group member utils', () => {
el = document.createElement('div');
el.setAttribute('data-members', membersJsonString);
el.setAttribute('data-group-id', '234');
+ el.setAttribute('data-can-manage-members', 'true');
});
afterEach(() => {
@@ -23,6 +24,7 @@ describe('group member utils', () => {
expect(parseDataAttributes(el)).toEqual({
members: membersParsed,
sourceId: 234,
+ canManageMembers: true,
});
});
});
diff --git a/spec/frontend/groups/store/groups_store_spec.js b/spec/frontend/groups/store/groups_store_spec.js
index 7d12f73d270..8ac5d7099f1 100644
--- a/spec/frontend/groups/store/groups_store_spec.js
+++ b/spec/frontend/groups/store/groups_store_spec.js
@@ -1,4 +1,5 @@
import GroupsStore from '~/groups/store/groups_store';
+import { getGroupItemMicrodata } from '~/groups/store/utils';
import {
mockGroups,
mockSearchedGroups,
@@ -17,9 +18,9 @@ describe('ProjectsStore', () => {
expect(Object.keys(store.state).length).toBe(2);
expect(Array.isArray(store.state.groups)).toBeTruthy();
expect(Object.keys(store.state.pageInfo).length).toBe(0);
- expect(store.hideProjects).not.toBeDefined();
+ expect(store.hideProjects).toBeFalsy();
- store = new GroupsStore(true);
+ store = new GroupsStore({ hideProjects: true });
expect(store.hideProjects).toBeTruthy();
});
@@ -86,22 +87,30 @@ describe('ProjectsStore', () => {
describe('formatGroupItem', () => {
it('should parse group item object and return updated object', () => {
- let store;
- let updatedGroupItem;
-
- store = new GroupsStore();
- updatedGroupItem = store.formatGroupItem(mockRawChildren[0]);
+ const store = new GroupsStore();
+ const updatedGroupItem = store.formatGroupItem(mockRawChildren[0]);
expect(Object.keys(updatedGroupItem).indexOf('fullName')).toBeGreaterThan(-1);
expect(updatedGroupItem.childrenCount).toBe(mockRawChildren[0].children_count);
expect(updatedGroupItem.isChildrenLoading).toBe(false);
expect(updatedGroupItem.isBeingRemoved).toBe(false);
+ expect(updatedGroupItem.microdata).toEqual({});
+ });
- store = new GroupsStore(true);
- updatedGroupItem = store.formatGroupItem(mockRawChildren[0]);
+ it('with hideProjects', () => {
+ const store = new GroupsStore({ hideProjects: true });
+ const updatedGroupItem = store.formatGroupItem(mockRawChildren[0]);
expect(Object.keys(updatedGroupItem).indexOf('fullName')).toBeGreaterThan(-1);
expect(updatedGroupItem.childrenCount).toBe(mockRawChildren[0].subgroup_count);
+ expect(updatedGroupItem.microdata).toEqual({});
+ });
+
+ it('with showSchemaMarkup', () => {
+ const store = new GroupsStore({ showSchemaMarkup: true });
+ const updatedGroupItem = store.formatGroupItem(mockRawChildren[0]);
+
+ expect(updatedGroupItem.microdata).toEqual(getGroupItemMicrodata(mockRawChildren[0]));
});
});
diff --git a/spec/frontend/groups/store/utils_spec.js b/spec/frontend/groups/store/utils_spec.js
new file mode 100644
index 00000000000..0961d4c72b4
--- /dev/null
+++ b/spec/frontend/groups/store/utils_spec.js
@@ -0,0 +1,44 @@
+import { getGroupItemMicrodata } from '~/groups/store/utils';
+
+describe('~/groups/store/utils', () => {
+ describe('getGroupItemMetadata', () => {
+ it('has default type', () => {
+ expect(getGroupItemMicrodata({ type: 'silly' })).toMatchInlineSnapshot(`
+ Object {
+ "descriptionItemprop": "description",
+ "imageItemprop": "image",
+ "itemprop": "owns",
+ "itemscope": true,
+ "itemtype": "https://schema.org/Thing",
+ "nameItemprop": "name",
+ }
+ `);
+ });
+
+ it('has group props', () => {
+ expect(getGroupItemMicrodata({ type: 'group' })).toMatchInlineSnapshot(`
+ Object {
+ "descriptionItemprop": "description",
+ "imageItemprop": "logo",
+ "itemprop": "subOrganization",
+ "itemscope": true,
+ "itemtype": "https://schema.org/Organization",
+ "nameItemprop": "name",
+ }
+ `);
+ });
+
+ it('has project props', () => {
+ expect(getGroupItemMicrodata({ type: 'project' })).toMatchInlineSnapshot(`
+ Object {
+ "descriptionItemprop": "description",
+ "imageItemprop": "image",
+ "itemprop": "owns",
+ "itemscope": true,
+ "itemtype": "https://schema.org/SoftwareSourceCode",
+ "nameItemprop": "name",
+ }
+ `);
+ });
+ });
+});
diff --git a/spec/frontend/helpers/stub_component.js b/spec/frontend/helpers/stub_component.js
new file mode 100644
index 00000000000..45550450517
--- /dev/null
+++ b/spec/frontend/helpers/stub_component.js
@@ -0,0 +1,12 @@
+export function stubComponent(Component, options = {}) {
+ return {
+ props: Component.props,
+ model: Component.model,
+ // Do not render any slots/scoped slots except default
+ // This differs from VTU behavior which renders all slots
+ template: '<div><slot></slot></div>',
+ // allows wrapper.find(Component) to work for stub
+ $_vueTestUtils_original: Component,
+ ...options,
+ };
+}
diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js
index ead898f04d3..0e9127b5c65 100644
--- a/spec/frontend/helpers/vue_test_utils_helper.js
+++ b/spec/frontend/helpers/vue_test_utils_helper.js
@@ -1,3 +1,5 @@
+import { isArray } from 'lodash';
+
const vNodeContainsText = (vnode, text) =>
(vnode.text && vnode.text.includes(text)) ||
(vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length);
@@ -34,9 +36,18 @@ export const waitForMutation = (store, expectedMutationType) =>
});
});
-export const extendedWrapper = wrapper =>
- Object.defineProperty(wrapper, 'findByTestId', {
+export const extendedWrapper = wrapper => {
+ if (isArray(wrapper) || !wrapper?.find) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ '[vue-test-utils-helper]: you are trying to extend an object that is not a VueWrapper.',
+ );
+ return wrapper;
+ }
+
+ return Object.defineProperty(wrapper, 'findByTestId', {
value(id) {
return this.find(`[data-testid="${id}"]`);
},
});
+};
diff --git a/spec/frontend/helpers/vue_test_utils_helper_spec.js b/spec/frontend/helpers/vue_test_utils_helper_spec.js
index 41714066da5..31c4ccd5dbb 100644
--- a/spec/frontend/helpers/vue_test_utils_helper_spec.js
+++ b/spec/frontend/helpers/vue_test_utils_helper_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { shallowWrapperContainsSlotText } from './vue_test_utils_helper';
+import { extendedWrapper, shallowWrapperContainsSlotText } from './vue_test_utils_helper';
describe('Vue test utils helpers', () => {
describe('shallowWrapperContainsSlotText', () => {
@@ -45,4 +45,48 @@ describe('Vue test utils helpers', () => {
expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false);
});
});
+
+ describe('extendedWrapper', () => {
+ describe('when an invalid wrapper is provided', () => {
+ beforeEach(() => {
+ // eslint-disable-next-line no-console
+ console.warn = jest.fn();
+ });
+
+ it.each`
+ wrapper
+ ${{}}
+ ${[]}
+ ${null}
+ ${undefined}
+ ${1}
+ ${''}
+ `('should warn with an error when the wrapper is $wrapper', ({ wrapper }) => {
+ extendedWrapper(wrapper);
+ /* eslint-disable no-console */
+ expect(console.warn).toHaveBeenCalled();
+ expect(console.warn).toHaveBeenCalledWith(
+ '[vue-test-utils-helper]: you are trying to extend an object that is not a VueWrapper.',
+ );
+ /* eslint-enable no-console */
+ });
+ });
+
+ describe('findByTestId', () => {
+ const testId = 'a-component';
+ let mockComponent;
+
+ beforeEach(() => {
+ mockComponent = extendedWrapper(
+ shallowMount({
+ template: `<div data-testid="${testId}"></div>`,
+ }),
+ );
+ });
+
+ it('should find the component by test id', () => {
+ expect(mockComponent.findByTestId(testId).exists()).toBe(true);
+ });
+ });
+ });
});
diff --git a/spec/frontend/helpers/vuex_action_helper.js b/spec/frontend/helpers/vuex_action_helper.js
index 6c3569a2247..64dd3888d47 100644
--- a/spec/frontend/helpers/vuex_action_helper.js
+++ b/spec/frontend/helpers/vuex_action_helper.js
@@ -4,7 +4,7 @@ const noop = () => {};
* Helper for testing action with expected mutations inspired in
* https://vuex.vuejs.org/en/testing.html
*
- * @param {Function} action to be tested
+ * @param {(Function|Object)} action to be tested, or object of named parameters
* @param {Object} payload will be provided to the action
* @param {Object} state will be provided to the action
* @param {Array} [expectedMutations=[]] mutations expected to be committed
@@ -39,15 +39,42 @@ const noop = () => {};
* [], // expected actions
* ).then(done)
* .catch(done.fail);
+ *
+ * @example
+ * await testAction({
+ * action: actions.actionName,
+ * payload: { deleteListId: 1 },
+ * state: { lists: [1, 2, 3] },
+ * expectedMutations: [ { type: types.MUTATION} ],
+ * expectedActions: [],
+ * })
*/
export default (
- action,
- payload,
- state,
- expectedMutations = [],
- expectedActions = [],
- done = noop,
+ actionArg,
+ payloadArg,
+ stateArg,
+ expectedMutationsArg = [],
+ expectedActionsArg = [],
+ doneArg = noop,
) => {
+ let action = actionArg;
+ let payload = payloadArg;
+ let state = stateArg;
+ let expectedMutations = expectedMutationsArg;
+ let expectedActions = expectedActionsArg;
+ let done = doneArg;
+
+ if (typeof actionArg !== 'function') {
+ ({
+ action,
+ payload,
+ state,
+ expectedMutations = [],
+ expectedActions = [],
+ done = noop,
+ } = actionArg);
+ }
+
const mutations = [];
const actions = [];
diff --git a/spec/frontend/helpers/vuex_action_helper_spec.js b/spec/frontend/helpers/vuex_action_helper_spec.js
index 61d05762a04..4d7bf21820a 100644
--- a/spec/frontend/helpers/vuex_action_helper_spec.js
+++ b/spec/frontend/helpers/vuex_action_helper_spec.js
@@ -1,166 +1,174 @@
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
-import testAction from './vuex_action_helper';
-
-describe('VueX test helper (testAction)', () => {
- let originalExpect;
- let assertion;
- let mock;
- const noop = () => {};
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- /**
- * In order to test the helper properly, we need to overwrite the Jest
- * `expect` helper. We test that the testAction helper properly passes the
- * dispatched actions/committed mutations to the Jest helper.
- */
- originalExpect = expect;
- assertion = null;
- global.expect = actual => ({
- toEqual: () => {
- originalExpect(actual).toEqual(assertion);
- },
- });
- });
+import testActionFn from './vuex_action_helper';
- afterEach(() => {
- mock.restore();
- global.expect = originalExpect;
- });
+const testActionFnWithOptionsArg = (...args) => {
+ const [action, payload, state, expectedMutations, expectedActions, done] = args;
+ return testActionFn({ action, payload, state, expectedMutations, expectedActions, done });
+};
- it('properly passes state and payload to action', () => {
- const exampleState = { FOO: 12, BAR: 3 };
- const examplePayload = { BAZ: 73, BIZ: 55 };
+describe.each([testActionFn, testActionFnWithOptionsArg])(
+ 'VueX test helper (testAction)',
+ testAction => {
+ let originalExpect;
+ let assertion;
+ let mock;
+ const noop = () => {};
- const action = ({ state }, payload) => {
- originalExpect(state).toEqual(exampleState);
- originalExpect(payload).toEqual(examplePayload);
- };
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ /**
+ * In order to test the helper properly, we need to overwrite the Jest
+ * `expect` helper. We test that the testAction helper properly passes the
+ * dispatched actions/committed mutations to the Jest helper.
+ */
+ originalExpect = expect;
+ assertion = null;
+ global.expect = actual => ({
+ toEqual: () => {
+ originalExpect(actual).toEqual(assertion);
+ },
+ });
+ });
- assertion = { mutations: [], actions: [] };
+ afterEach(() => {
+ mock.restore();
+ global.expect = originalExpect;
+ });
- testAction(action, examplePayload, exampleState);
- });
+ it('properly passes state and payload to action', () => {
+ const exampleState = { FOO: 12, BAR: 3 };
+ const examplePayload = { BAZ: 73, BIZ: 55 };
- describe('given a sync action', () => {
- it('mocks committing mutations', () => {
- const action = ({ commit }) => {
- commit('MUTATION');
+ const action = ({ state }, payload) => {
+ originalExpect(state).toEqual(exampleState);
+ originalExpect(payload).toEqual(examplePayload);
};
- assertion = { mutations: [{ type: 'MUTATION' }], actions: [] };
+ assertion = { mutations: [], actions: [] };
- testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
+ testAction(action, examplePayload, exampleState);
});
- it('mocks dispatching actions', () => {
- const action = ({ dispatch }) => {
- dispatch('ACTION');
- };
+ describe('given a sync action', () => {
+ it('mocks committing mutations', () => {
+ const action = ({ commit }) => {
+ commit('MUTATION');
+ };
- assertion = { actions: [{ type: 'ACTION' }], mutations: [] };
+ assertion = { mutations: [{ type: 'MUTATION' }], actions: [] };
- testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
- });
+ testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
+ });
- it('works with done callback once finished', done => {
- assertion = { mutations: [], actions: [] };
+ it('mocks dispatching actions', () => {
+ const action = ({ dispatch }) => {
+ dispatch('ACTION');
+ };
- testAction(noop, null, {}, assertion.mutations, assertion.actions, done);
- });
+ assertion = { actions: [{ type: 'ACTION' }], mutations: [] };
- it('returns a promise', done => {
- assertion = { mutations: [], actions: [] };
+ testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
+ });
- testAction(noop, null, {}, assertion.mutations, assertion.actions)
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('given an async action (returning a promise)', () => {
- let lastError;
- const data = { FOO: 'BAR' };
-
- const asyncAction = ({ commit, dispatch }) => {
- dispatch('ACTION');
-
- return axios
- .get(TEST_HOST)
- .catch(error => {
- commit('ERROR');
- lastError = error;
- throw error;
- })
- .then(() => {
- commit('SUCCESS');
- return data;
- });
- };
+ it('works with done callback once finished', done => {
+ assertion = { mutations: [], actions: [] };
- beforeEach(() => {
- lastError = null;
+ testAction(noop, null, {}, assertion.mutations, assertion.actions, done);
+ });
+
+ it('returns a promise', done => {
+ assertion = { mutations: [], actions: [] };
+
+ testAction(noop, null, {}, assertion.mutations, assertion.actions)
+ .then(done)
+ .catch(done.fail);
+ });
});
- it('works with done callback once finished', done => {
- mock.onGet(TEST_HOST).replyOnce(200, 42);
+ describe('given an async action (returning a promise)', () => {
+ let lastError;
+ const data = { FOO: 'BAR' };
- assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
+ const asyncAction = ({ commit, dispatch }) => {
+ dispatch('ACTION');
- testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done);
- });
+ return axios
+ .get(TEST_HOST)
+ .catch(error => {
+ commit('ERROR');
+ lastError = error;
+ throw error;
+ })
+ .then(() => {
+ commit('SUCCESS');
+ return data;
+ });
+ };
- it('returns original data of successful promise while checking actions/mutations', done => {
- mock.onGet(TEST_HOST).replyOnce(200, 42);
+ beforeEach(() => {
+ lastError = null;
+ });
- assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
+ it('works with done callback once finished', done => {
+ mock.onGet(TEST_HOST).replyOnce(200, 42);
- testAction(asyncAction, null, {}, assertion.mutations, assertion.actions)
- .then(res => {
- originalExpect(res).toEqual(data);
- done();
- })
- .catch(done.fail);
- });
+ assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
+
+ testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done);
+ });
+
+ it('returns original data of successful promise while checking actions/mutations', done => {
+ mock.onGet(TEST_HOST).replyOnce(200, 42);
- it('returns original error of rejected promise while checking actions/mutations', done => {
- mock.onGet(TEST_HOST).replyOnce(500, '');
+ assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
- assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] };
+ testAction(asyncAction, null, {}, assertion.mutations, assertion.actions)
+ .then(res => {
+ originalExpect(res).toEqual(data);
+ done();
+ })
+ .catch(done.fail);
+ });
- testAction(asyncAction, null, {}, assertion.mutations, assertion.actions)
- .then(done.fail)
- .catch(error => {
- originalExpect(error).toBe(lastError);
- done();
- });
+ it('returns original error of rejected promise while checking actions/mutations', done => {
+ mock.onGet(TEST_HOST).replyOnce(500, '');
+
+ assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] };
+
+ testAction(asyncAction, null, {}, assertion.mutations, assertion.actions)
+ .then(done.fail)
+ .catch(error => {
+ originalExpect(error).toBe(lastError);
+ done();
+ });
+ });
});
- });
- it('works with async actions not returning promises', done => {
- const data = { FOO: 'BAR' };
+ it('works with async actions not returning promises', done => {
+ const data = { FOO: 'BAR' };
- const asyncAction = ({ commit, dispatch }) => {
- dispatch('ACTION');
+ const asyncAction = ({ commit, dispatch }) => {
+ dispatch('ACTION');
- axios
- .get(TEST_HOST)
- .then(() => {
- commit('SUCCESS');
- return data;
- })
- .catch(error => {
- commit('ERROR');
- throw error;
- });
- };
+ axios
+ .get(TEST_HOST)
+ .then(() => {
+ commit('SUCCESS');
+ return data;
+ })
+ .catch(error => {
+ commit('ERROR');
+ throw error;
+ });
+ };
- mock.onGet(TEST_HOST).replyOnce(200, 42);
+ mock.onGet(TEST_HOST).replyOnce(200, 42);
- assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
+ assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
- testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done);
- });
-});
+ testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done);
+ });
+ },
+);
diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js
index ff3852b6775..315298eaf26 100644
--- a/spec/frontend/ide/components/ide_spec.js
+++ b/spec/frontend/ide/components/ide_spec.js
@@ -1,15 +1,8 @@
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlButton, GlLoadingIcon } from '@gitlab/ui';
+import waitForPromises from 'helpers/wait_for_promises';
import { createStore } from '~/ide/stores';
import ErrorMessage from '~/ide/components/error_message.vue';
-import FindFile from '~/vue_shared/components/file_finder/index.vue';
-import CommitEditorHeader from '~/ide/components/commit_sidebar/editor_header.vue';
-import RepoTabs from '~/ide/components/repo_tabs.vue';
-import IdeStatusBar from '~/ide/components/ide_status_bar.vue';
-import RightPane from '~/ide/components/panes/right.vue';
-import NewModal from '~/ide/components/new_dropdown/modal.vue';
-
import ide from '~/ide/components/ide.vue';
import { file } from '../helpers';
import { projectData } from '../mock_data';
@@ -39,17 +32,6 @@ describe('WebIDE', () => {
return shallowMount(ide, {
store,
localVue,
- stubs: {
- ErrorMessage,
- GlButton,
- GlLoadingIcon,
- CommitEditorHeader,
- RepoTabs,
- IdeStatusBar,
- FindFile,
- RightPane,
- NewModal,
- },
});
}
@@ -74,27 +56,24 @@ describe('WebIDE', () => {
describe('ide component, non-empty repo', () => {
describe('error message', () => {
- it('does not show error message when it is not set', () => {
- wrapper = createComponent({
- state: {
- errorMessage: null,
- },
- });
-
- expect(wrapper.find(ErrorMessage).exists()).toBe(false);
- });
-
- it('shows error message when set', () => {
- wrapper = createComponent({
- state: {
- errorMessage: {
- text: 'error',
+ it.each`
+ errorMessage | exists
+ ${null} | ${false}
+ ${{ text: 'error' }} | ${true}
+ `(
+ 'should error message exists=$exists when errorMessage=$errorMessage',
+ async ({ errorMessage, exists }) => {
+ wrapper = createComponent({
+ state: {
+ errorMessage,
},
- },
- });
+ });
- expect(wrapper.find(ErrorMessage).exists()).toBe(true);
- });
+ await waitForPromises();
+
+ expect(wrapper.find(ErrorMessage).exists()).toBe(exists);
+ },
+ );
});
describe('onBeforeUnload', () => {
diff --git a/spec/frontend/ide/components/terminal/session_spec.js b/spec/frontend/ide/components/terminal/session_spec.js
index ce61a31691a..3ca37166ac4 100644
--- a/spec/frontend/ide/components/terminal/session_spec.js
+++ b/spec/frontend/ide/components/terminal/session_spec.js
@@ -1,4 +1,5 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
import Vuex from 'vuex';
import TerminalSession from '~/ide/components/terminal/session.vue';
import Terminal from '~/ide/components/terminal/terminal.vue';
@@ -38,6 +39,8 @@ describe('IDE TerminalSession', () => {
});
};
+ const findButton = () => wrapper.find(GlButton);
+
beforeEach(() => {
state = {
session: { status: RUNNING, terminalPath: TEST_TERMINAL_PATH },
@@ -69,8 +72,8 @@ describe('IDE TerminalSession', () => {
state.session = { status };
factory();
- const button = wrapper.find('button');
- button.trigger('click');
+ const button = findButton();
+ button.vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(button.text()).toEqual('Stop Terminal');
@@ -84,8 +87,8 @@ describe('IDE TerminalSession', () => {
state.session = { status };
factory();
- const button = wrapper.find('button');
- button.trigger('click');
+ const button = findButton();
+ button.vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(button.text()).toEqual('Restart Terminal');
diff --git a/spec/frontend/ide/components/terminal/view_spec.js b/spec/frontend/ide/components/terminal/view_spec.js
index eff200550da..37f7957c526 100644
--- a/spec/frontend/ide/components/terminal/view_spec.js
+++ b/spec/frontend/ide/components/terminal/view_spec.js
@@ -1,6 +1,7 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { TEST_HOST } from 'spec/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
import TerminalEmptyState from '~/ide/components/terminal/empty_state.vue';
import TerminalView from '~/ide/components/terminal/view.vue';
import TerminalSession from '~/ide/components/terminal/session.vue';
@@ -17,7 +18,7 @@ describe('IDE TerminalView', () => {
let getters;
let wrapper;
- const factory = () => {
+ const factory = async () => {
const store = new Vuex.Store({
modules: {
terminal: {
@@ -30,6 +31,9 @@ describe('IDE TerminalView', () => {
});
wrapper = shallowMount(TerminalView, { localVue, store });
+
+ // Uses deferred components, so wait for those to load...
+ await waitForPromises();
};
beforeEach(() => {
@@ -59,8 +63,8 @@ describe('IDE TerminalView', () => {
wrapper.destroy();
});
- it('renders empty state', () => {
- factory();
+ it('renders empty state', async () => {
+ await factory();
expect(wrapper.find(TerminalEmptyState).props()).toEqual({
helpPath: TEST_HELP_PATH,
@@ -69,8 +73,8 @@ describe('IDE TerminalView', () => {
});
});
- it('hides splash and starts, when started', () => {
- factory();
+ it('hides splash and starts, when started', async () => {
+ await factory();
expect(actions.startSession).not.toHaveBeenCalled();
expect(actions.hideSplash).not.toHaveBeenCalled();
@@ -81,9 +85,9 @@ describe('IDE TerminalView', () => {
expect(actions.hideSplash).toHaveBeenCalled();
});
- it('shows Web Terminal when started', () => {
+ it('shows Web Terminal when started', async () => {
state.isShowSplash = false;
- factory();
+ await factory();
expect(wrapper.find(TerminalEmptyState).exists()).toBe(false);
expect(wrapper.find(TerminalSession).exists()).toBe(true);
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index cc290fc526e..744ac086b5f 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -510,8 +510,6 @@ describe('IDE store file actions', () => {
describe('changeFileContent', () => {
let tmpFile;
- const callAction = (content = 'content\n') =>
- store.dispatch('changeFileContent', { path: tmpFile.path, content });
beforeEach(() => {
tmpFile = file('tmpFile');
@@ -521,11 +519,23 @@ describe('IDE store file actions', () => {
});
it('updates file content', () => {
- return callAction().then(() => {
+ const content = 'content\n';
+
+ return store.dispatch('changeFileContent', { path: tmpFile.path, content }).then(() => {
expect(tmpFile.content).toBe('content\n');
});
});
+ it('does nothing if path does not exist', () => {
+ const content = 'content\n';
+
+ return store
+ .dispatch('changeFileContent', { path: 'not/a/real_file.txt', content })
+ .then(() => {
+ expect(tmpFile.content).toBe('\n');
+ });
+ });
+
it('adds file into stagedFiles array', () => {
return store
.dispatch('changeFileContent', {
diff --git a/spec/frontend/ide/utils_spec.js b/spec/frontend/ide/utils_spec.js
index 6cd2128d356..3b772c0b259 100644
--- a/spec/frontend/ide/utils_spec.js
+++ b/spec/frontend/ide/utils_spec.js
@@ -4,7 +4,6 @@ import {
registerLanguages,
registerSchema,
trimPathComponents,
- insertFinalNewline,
trimTrailingWhitespace,
getPathParents,
getPathParent,
@@ -225,29 +224,6 @@ describe('WebIDE utils', () => {
});
});
- describe('addFinalNewline', () => {
- it.each`
- input | output
- ${'some text'} | ${'some text\n'}
- ${'some text\n'} | ${'some text\n'}
- ${'some text\n\n'} | ${'some text\n\n'}
- ${'some\n text'} | ${'some\n text\n'}
- `('adds a newline if it doesnt already exist for input: $input', ({ input, output }) => {
- expect(insertFinalNewline(input)).toBe(output);
- });
-
- it.each`
- input | output
- ${'some text'} | ${'some text\r\n'}
- ${'some text\r\n'} | ${'some text\r\n'}
- ${'some text\n'} | ${'some text\n\r\n'}
- ${'some text\r\n\r\n'} | ${'some text\r\n\r\n'}
- ${'some\r\n text'} | ${'some\r\n text\r\n'}
- `('works with CRLF newline style; input: $input', ({ input, output }) => {
- expect(insertFinalNewline(input, '\r\n')).toBe(output);
- });
- });
-
describe('getPathParents', () => {
it.each`
path | parents
diff --git a/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js b/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js
new file mode 100644
index 00000000000..d88a31a0e47
--- /dev/null
+++ b/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js
@@ -0,0 +1,112 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton, GlLink, GlFormInput } from '@gitlab/ui';
+import Select2Select from '~/vue_shared/components/select2_select.vue';
+import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue';
+import { STATUSES } from '~/import_entities/constants';
+import { availableNamespacesFixture } from '../graphql/fixtures';
+
+const getFakeGroup = status => ({
+ web_url: 'https://fake.host/',
+ full_path: 'fake_group_1',
+ full_name: 'fake_name_1',
+ import_target: {
+ target_namespace: 'root',
+ new_name: 'group1',
+ },
+ id: 1,
+ status,
+});
+
+describe('import table row', () => {
+ let wrapper;
+ let group;
+
+ const findByText = (cmp, text) => {
+ return wrapper.findAll(cmp).wrappers.find(node => node.text().indexOf(text) === 0);
+ };
+ const findImportButton = () => findByText(GlButton, 'Import');
+ const findNameInput = () => wrapper.find(GlFormInput);
+ const findNamespaceDropdown = () => wrapper.find(Select2Select);
+
+ const createComponent = props => {
+ wrapper = shallowMount(ImportTableRow, {
+ propsData: {
+ availableNamespaces: availableNamespacesFixture,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('events', () => {
+ beforeEach(() => {
+ group = getFakeGroup(STATUSES.NONE);
+ createComponent({ group });
+ });
+
+ it.each`
+ selector | sourceEvent | payload | event
+ ${findNamespaceDropdown} | ${'input'} | ${'demo'} | ${'update-target-namespace'}
+ ${findNameInput} | ${'input'} | ${'demo'} | ${'update-new-name'}
+ ${findImportButton} | ${'click'} | ${undefined} | ${'import-group'}
+ `('invokes $event', ({ selector, sourceEvent, payload, event }) => {
+ selector().vm.$emit(sourceEvent, payload);
+ expect(wrapper.emitted(event)).toBeDefined();
+ expect(wrapper.emitted(event)[0][0]).toBe(payload);
+ });
+ });
+
+ describe('when entity status is NONE', () => {
+ beforeEach(() => {
+ group = getFakeGroup(STATUSES.NONE);
+ createComponent({ group });
+ });
+
+ it('renders Import button', () => {
+ expect(findByText(GlButton, 'Import').exists()).toBe(true);
+ });
+
+ it('renders namespace dropdown as not disabled', () => {
+ expect(findNamespaceDropdown().attributes('disabled')).toBe(undefined);
+ });
+ });
+
+ describe('when entity status is SCHEDULING', () => {
+ beforeEach(() => {
+ group = getFakeGroup(STATUSES.SCHEDULING);
+ createComponent({ group });
+ });
+
+ it('does not render Import button', () => {
+ expect(findByText(GlButton, 'Import')).toBe(undefined);
+ });
+
+ it('renders namespace dropdown as disabled', () => {
+ expect(findNamespaceDropdown().attributes('disabled')).toBe('true');
+ });
+ });
+
+ describe('when entity status is FINISHED', () => {
+ beforeEach(() => {
+ group = getFakeGroup(STATUSES.FINISHED);
+ createComponent({ group });
+ });
+
+ it('does not render Import button', () => {
+ expect(findByText(GlButton, 'Import')).toBe(undefined);
+ });
+
+ it('does not render namespace dropdown', () => {
+ expect(findNamespaceDropdown().exists()).toBe(false);
+ });
+
+ it('renders target as link', () => {
+ const TARGET_LINK = `${group.import_target.target_namespace}/${group.import_target.new_name}`;
+ expect(findByText(GlLink, TARGET_LINK).exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/import_entities/import_groups/components/import_table_spec.js b/spec/frontend/import_entities/import_groups/components/import_table_spec.js
new file mode 100644
index 00000000000..0ca721cd951
--- /dev/null
+++ b/spec/frontend/import_entities/import_groups/components/import_table_spec.js
@@ -0,0 +1,103 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import { GlLoadingIcon } from '@gitlab/ui';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
+import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue';
+import ImportTable from '~/import_entities/import_groups/components/import_table.vue';
+import setTargetNamespaceMutation from '~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql';
+import setNewNameMutation from '~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql';
+import importGroupMutation from '~/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql';
+
+import { STATUSES } from '~/import_entities/constants';
+
+import { availableNamespacesFixture, generateFakeEntry } from '../graphql/fixtures';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('import table', () => {
+ let wrapper;
+ let apolloProvider;
+
+ const createComponent = ({ bulkImportSourceGroups }) => {
+ apolloProvider = createMockApollo([], {
+ Query: {
+ availableNamespaces: () => availableNamespacesFixture,
+ bulkImportSourceGroups,
+ },
+ Mutation: {
+ setTargetNamespace: jest.fn(),
+ setNewName: jest.fn(),
+ importGroup: jest.fn(),
+ },
+ });
+
+ wrapper = shallowMount(ImportTable, {
+ localVue,
+ apolloProvider,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders loading icon while performing request', async () => {
+ createComponent({
+ bulkImportSourceGroups: () => new Promise(() => {}),
+ });
+ await waitForPromises();
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ it('does not renders loading icon when request is completed', async () => {
+ createComponent({
+ bulkImportSourceGroups: () => [],
+ });
+ await waitForPromises();
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ });
+
+ it('renders import row for each group in response', async () => {
+ const FAKE_GROUPS = [
+ generateFakeEntry({ id: 1, status: STATUSES.NONE }),
+ generateFakeEntry({ id: 2, status: STATUSES.FINISHED }),
+ ];
+ createComponent({
+ bulkImportSourceGroups: () => FAKE_GROUPS,
+ });
+ await waitForPromises();
+
+ expect(wrapper.findAll(ImportTableRow)).toHaveLength(FAKE_GROUPS.length);
+ });
+
+ describe('converts row events to mutation invocations', () => {
+ const FAKE_GROUP = generateFakeEntry({ id: 1, status: STATUSES.NONE });
+
+ beforeEach(() => {
+ createComponent({
+ bulkImportSourceGroups: () => [FAKE_GROUP],
+ });
+ return waitForPromises();
+ });
+
+ it.each`
+ event | payload | mutation | variables
+ ${'update-target-namespace'} | ${'new-namespace'} | ${setTargetNamespaceMutation} | ${{ sourceGroupId: FAKE_GROUP.id, targetNamespace: 'new-namespace' }}
+ ${'update-new-name'} | ${'new-name'} | ${setNewNameMutation} | ${{ sourceGroupId: FAKE_GROUP.id, newName: 'new-name' }}
+ ${'import-group'} | ${undefined} | ${importGroupMutation} | ${{ sourceGroupId: FAKE_GROUP.id }}
+ `('correctly maps $event to mutation', async ({ event, payload, mutation, variables }) => {
+ jest.spyOn(apolloProvider.defaultClient, 'mutate');
+ wrapper.find(ImportTableRow).vm.$emit(event, payload);
+ await waitForPromises();
+ expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({
+ mutation,
+ variables,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
new file mode 100644
index 00000000000..cacbe358a62
--- /dev/null
+++ b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
@@ -0,0 +1,221 @@
+import MockAdapter from 'axios-mock-adapter';
+import { InMemoryCache } from 'apollo-cache-inmemory';
+import { createMockClient } from 'mock-apollo-client';
+import waitForPromises from 'helpers/wait_for_promises';
+import axios from '~/lib/utils/axios_utils';
+import {
+ clientTypenames,
+ createResolvers,
+} from '~/import_entities/import_groups/graphql/client_factory';
+import { StatusPoller } from '~/import_entities/import_groups/graphql/services/status_poller';
+import { STATUSES } from '~/import_entities/constants';
+
+import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql';
+import availableNamespacesQuery from '~/import_entities/import_groups/graphql/queries/available_namespaces.query.graphql';
+import setTargetNamespaceMutation from '~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql';
+import setNewNameMutation from '~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql';
+import importGroupMutation from '~/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql';
+import httpStatus from '~/lib/utils/http_status';
+import { statusEndpointFixture, availableNamespacesFixture } from './fixtures';
+
+jest.mock('~/import_entities/import_groups/graphql/services/status_poller', () => ({
+ StatusPoller: jest.fn().mockImplementation(function mock() {
+ this.startPolling = jest.fn();
+ }),
+}));
+
+const FAKE_ENDPOINTS = {
+ status: '/fake_status_url',
+ availableNamespaces: '/fake_available_namespaces',
+ createBulkImport: '/fake_create_bulk_import',
+};
+
+describe('Bulk import resolvers', () => {
+ let axiosMockAdapter;
+ let client;
+
+ beforeEach(() => {
+ axiosMockAdapter = new MockAdapter(axios);
+ client = createMockClient({
+ cache: new InMemoryCache({
+ fragmentMatcher: { match: () => true },
+ addTypename: false,
+ }),
+ resolvers: createResolvers({ endpoints: FAKE_ENDPOINTS }),
+ });
+ });
+
+ afterEach(() => {
+ axiosMockAdapter.restore();
+ });
+
+ describe('queries', () => {
+ describe('availableNamespaces', () => {
+ let results;
+
+ beforeEach(async () => {
+ axiosMockAdapter
+ .onGet(FAKE_ENDPOINTS.availableNamespaces)
+ .reply(httpStatus.OK, availableNamespacesFixture);
+
+ const response = await client.query({ query: availableNamespacesQuery });
+ results = response.data.availableNamespaces;
+ });
+
+ it('mirrors REST endpoint response fields', () => {
+ const extractRelevantFields = obj => ({ id: obj.id, full_path: obj.full_path });
+
+ expect(results.map(extractRelevantFields)).toStrictEqual(
+ availableNamespacesFixture.map(extractRelevantFields),
+ );
+ });
+ });
+
+ describe('bulkImportSourceGroups', () => {
+ let results;
+
+ beforeEach(async () => {
+ axiosMockAdapter.onGet(FAKE_ENDPOINTS.status).reply(httpStatus.OK, statusEndpointFixture);
+ axiosMockAdapter
+ .onGet(FAKE_ENDPOINTS.availableNamespaces)
+ .reply(httpStatus.OK, availableNamespacesFixture);
+
+ const response = await client.query({ query: bulkImportSourceGroupsQuery });
+ results = response.data.bulkImportSourceGroups;
+ });
+
+ it('mirrors REST endpoint response fields', () => {
+ const MIRRORED_FIELDS = ['id', 'full_name', 'full_path', 'web_url'];
+ expect(
+ results.every((r, idx) =>
+ MIRRORED_FIELDS.every(
+ field => r[field] === statusEndpointFixture.importable_data[idx][field],
+ ),
+ ),
+ ).toBe(true);
+ });
+
+ it('populates each result instance with status field default to none', () => {
+ expect(results.every(r => r.status === STATUSES.NONE)).toBe(true);
+ });
+
+ it('populates each result instance with import_target defaulted to first available namespace', () => {
+ expect(
+ results.every(
+ r => r.import_target.target_namespace === availableNamespacesFixture[0].full_path,
+ ),
+ ).toBe(true);
+ });
+ });
+ });
+
+ describe('mutations', () => {
+ let results;
+ const GROUP_ID = 1;
+
+ beforeEach(() => {
+ client.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: {
+ bulkImportSourceGroups: [
+ {
+ __typename: clientTypenames.BulkImportSourceGroup,
+ id: GROUP_ID,
+ status: STATUSES.NONE,
+ web_url: 'https://fake.host/1',
+ full_path: 'fake_group_1',
+ full_name: 'fake_name_1',
+ import_target: {
+ target_namespace: 'root',
+ new_name: 'group1',
+ },
+ },
+ ],
+ },
+ });
+
+ client
+ .watchQuery({
+ query: bulkImportSourceGroupsQuery,
+ fetchPolicy: 'cache-only',
+ })
+ .subscribe(({ data }) => {
+ results = data.bulkImportSourceGroups;
+ });
+ });
+
+ it('setTargetNamespaces updates group target namespace', async () => {
+ const NEW_TARGET_NAMESPACE = 'target';
+ await client.mutate({
+ mutation: setTargetNamespaceMutation,
+ variables: { sourceGroupId: GROUP_ID, targetNamespace: NEW_TARGET_NAMESPACE },
+ });
+
+ expect(results[0].import_target.target_namespace).toBe(NEW_TARGET_NAMESPACE);
+ });
+
+ it('setNewName updates group target name', async () => {
+ const NEW_NAME = 'new';
+ await client.mutate({
+ mutation: setNewNameMutation,
+ variables: { sourceGroupId: GROUP_ID, newName: NEW_NAME },
+ });
+
+ expect(results[0].import_target.new_name).toBe(NEW_NAME);
+ });
+
+ describe('importGroup', () => {
+ it('sets status to SCHEDULING when request initiates', async () => {
+ axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(() => new Promise(() => {}));
+
+ client.mutate({
+ mutation: importGroupMutation,
+ variables: { sourceGroupId: GROUP_ID },
+ });
+ await waitForPromises();
+
+ const { bulkImportSourceGroups: intermediateResults } = client.readQuery({
+ query: bulkImportSourceGroupsQuery,
+ });
+
+ expect(intermediateResults[0].status).toBe(STATUSES.SCHEDULING);
+ });
+
+ it('sets group status to STARTED when request completes', async () => {
+ axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK);
+ await client.mutate({
+ mutation: importGroupMutation,
+ variables: { sourceGroupId: GROUP_ID },
+ });
+
+ expect(results[0].status).toBe(STATUSES.STARTED);
+ });
+
+ it('starts polling when request completes', async () => {
+ axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK);
+ await client.mutate({
+ mutation: importGroupMutation,
+ variables: { sourceGroupId: GROUP_ID },
+ });
+ const [statusPoller] = StatusPoller.mock.instances;
+ expect(statusPoller.startPolling).toHaveBeenCalled();
+ });
+
+ it('resets status to NONE if request fails', async () => {
+ axiosMockAdapter
+ .onPost(FAKE_ENDPOINTS.createBulkImport)
+ .reply(httpStatus.INTERNAL_SERVER_ERROR);
+
+ client
+ .mutate({
+ mutation: importGroupMutation,
+ variables: { sourceGroupId: GROUP_ID },
+ })
+ .catch(() => {});
+ await waitForPromises();
+
+ expect(results[0].status).toBe(STATUSES.NONE);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/import_entities/import_groups/graphql/fixtures.js b/spec/frontend/import_entities/import_groups/graphql/fixtures.js
new file mode 100644
index 00000000000..62e9581bd2d
--- /dev/null
+++ b/spec/frontend/import_entities/import_groups/graphql/fixtures.js
@@ -0,0 +1,51 @@
+import { clientTypenames } from '~/import_entities/import_groups/graphql/client_factory';
+
+export const generateFakeEntry = ({ id, status, ...rest }) => ({
+ __typename: clientTypenames.BulkImportSourceGroup,
+ web_url: `https://fake.host/${id}`,
+ full_path: `fake_group_${id}`,
+ full_name: `fake_name_${id}`,
+ import_target: {
+ target_namespace: 'root',
+ new_name: `group${id}`,
+ },
+ id,
+ status,
+ ...rest,
+});
+
+export const statusEndpointFixture = {
+ importable_data: [
+ {
+ id: 2595438,
+ full_name: 'AutoBreakfast',
+ full_path: 'auto-breakfast',
+ web_url: 'https://gitlab.com/groups/auto-breakfast',
+ },
+ {
+ id: 4347861,
+ full_name: 'GitLab Data',
+ full_path: 'gitlab-data',
+ web_url: 'https://gitlab.com/groups/gitlab-data',
+ },
+ {
+ id: 5723700,
+ full_name: 'GitLab Services',
+ full_path: 'gitlab-services',
+ web_url: 'https://gitlab.com/groups/gitlab-services',
+ },
+ {
+ id: 349181,
+ full_name: 'GitLab-examples',
+ full_path: 'gitlab-examples',
+ web_url: 'https://gitlab.com/groups/gitlab-examples',
+ },
+ ],
+};
+
+export const availableNamespacesFixture = [
+ { id: 24, full_path: 'Commit451' },
+ { id: 22, full_path: 'gitlab-org' },
+ { id: 23, full_path: 'gnuwget' },
+ { id: 25, full_path: 'jashkenas' },
+];
diff --git a/spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js b/spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js
new file mode 100644
index 00000000000..5940ea544ea
--- /dev/null
+++ b/spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js
@@ -0,0 +1,82 @@
+import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
+import { SourceGroupsManager } from '~/import_entities/import_groups/graphql/services/source_groups_manager';
+import ImportSourceGroupFragment from '~/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql';
+import { clientTypenames } from '~/import_entities/import_groups/graphql/client_factory';
+
+describe('SourceGroupsManager', () => {
+ let manager;
+ let client;
+
+ const getFakeGroup = () => ({
+ __typename: clientTypenames.BulkImportSourceGroup,
+ id: 5,
+ });
+
+ beforeEach(() => {
+ client = {
+ readFragment: jest.fn(),
+ writeFragment: jest.fn(),
+ };
+
+ manager = new SourceGroupsManager({ client });
+ });
+
+ it('finds item by group id', () => {
+ const ID = 5;
+
+ const FAKE_GROUP = getFakeGroup();
+ client.readFragment.mockReturnValue(FAKE_GROUP);
+ const group = manager.findById(ID);
+ expect(group).toBe(FAKE_GROUP);
+ expect(client.readFragment).toHaveBeenCalledWith({
+ fragment: ImportSourceGroupFragment,
+ id: defaultDataIdFromObject(getFakeGroup()),
+ });
+ });
+
+ it('updates group with provided function', () => {
+ const UPDATED_GROUP = {};
+ const fn = jest.fn().mockReturnValue(UPDATED_GROUP);
+ manager.update(getFakeGroup(), fn);
+
+ expect(client.writeFragment).toHaveBeenCalledWith({
+ fragment: ImportSourceGroupFragment,
+ id: defaultDataIdFromObject(getFakeGroup()),
+ data: UPDATED_GROUP,
+ });
+ });
+
+ it('updates group by id with provided function', () => {
+ const UPDATED_GROUP = {};
+ const fn = jest.fn().mockReturnValue(UPDATED_GROUP);
+ client.readFragment.mockReturnValue(getFakeGroup());
+ manager.updateById(getFakeGroup().id, fn);
+
+ expect(client.readFragment).toHaveBeenCalledWith({
+ fragment: ImportSourceGroupFragment,
+ id: defaultDataIdFromObject(getFakeGroup()),
+ });
+
+ expect(client.writeFragment).toHaveBeenCalledWith({
+ fragment: ImportSourceGroupFragment,
+ id: defaultDataIdFromObject(getFakeGroup()),
+ data: UPDATED_GROUP,
+ });
+ });
+
+ it('sets import status when group is provided', () => {
+ client.readFragment.mockReturnValue(getFakeGroup());
+
+ const NEW_STATUS = 'NEW_STATUS';
+ manager.setImportStatus(getFakeGroup(), NEW_STATUS);
+
+ expect(client.writeFragment).toHaveBeenCalledWith({
+ fragment: ImportSourceGroupFragment,
+ id: defaultDataIdFromObject(getFakeGroup()),
+ data: {
+ ...getFakeGroup(),
+ status: NEW_STATUS,
+ },
+ });
+ });
+});
diff --git a/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js b/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js
new file mode 100644
index 00000000000..8eb1ffb3cd0
--- /dev/null
+++ b/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js
@@ -0,0 +1,213 @@
+import { createMockClient } from 'mock-apollo-client';
+import { InMemoryCache } from 'apollo-cache-inmemory';
+import waitForPromises from 'helpers/wait_for_promises';
+
+import createFlash from '~/flash';
+import { StatusPoller } from '~/import_entities/import_groups/graphql/services/status_poller';
+import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql';
+import { STATUSES } from '~/import_entities/constants';
+import { SourceGroupsManager } from '~/import_entities/import_groups/graphql/services/source_groups_manager';
+import { generateFakeEntry } from '../fixtures';
+
+jest.mock('~/flash');
+jest.mock('~/import_entities/import_groups/graphql/services/source_groups_manager', () => ({
+ SourceGroupsManager: jest.fn().mockImplementation(function mock() {
+ this.setImportStatus = jest.fn();
+ }),
+}));
+
+const TEST_POLL_INTERVAL = 1000;
+
+describe('Bulk import status poller', () => {
+ let poller;
+ let clientMock;
+
+ const listQueryCacheCalls = () =>
+ clientMock.readQuery.mock.calls.filter(call => call[0].query === bulkImportSourceGroupsQuery);
+
+ beforeEach(() => {
+ clientMock = createMockClient({
+ cache: new InMemoryCache({
+ fragmentMatcher: { match: () => true },
+ }),
+ });
+
+ jest.spyOn(clientMock, 'readQuery');
+
+ poller = new StatusPoller({
+ client: clientMock,
+ interval: TEST_POLL_INTERVAL,
+ });
+ });
+
+ describe('general behavior', () => {
+ beforeEach(() => {
+ clientMock.cache.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: { bulkImportSourceGroups: [] },
+ });
+ });
+
+ it('does not perform polling when constructed', () => {
+ jest.runOnlyPendingTimers();
+ expect(listQueryCacheCalls()).toHaveLength(0);
+ });
+
+ it('immediately start polling when requested', async () => {
+ await poller.startPolling();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+ });
+
+ it('constantly polls when started', async () => {
+ poller.startPolling();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+
+ jest.advanceTimersByTime(TEST_POLL_INTERVAL);
+ expect(listQueryCacheCalls()).toHaveLength(2);
+
+ jest.advanceTimersByTime(TEST_POLL_INTERVAL);
+ expect(listQueryCacheCalls()).toHaveLength(3);
+ });
+
+ it('does not start polling when requested multiple times', async () => {
+ poller.startPolling();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+
+ poller.startPolling();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+ });
+
+ it('stops polling when requested', async () => {
+ poller.startPolling();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+
+ poller.stopPolling();
+ jest.runOnlyPendingTimers();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+ });
+
+ it('does not query server when list is empty', async () => {
+ jest.spyOn(clientMock, 'query');
+ poller.startPolling();
+ expect(clientMock.query).not.toHaveBeenCalled();
+ });
+ });
+
+ it('does not query server when no groups have STARTED status', async () => {
+ clientMock.cache.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: {
+ bulkImportSourceGroups: [STATUSES.NONE, STATUSES.FINISHED].map((status, idx) =>
+ generateFakeEntry({ status, id: idx }),
+ ),
+ },
+ });
+
+ jest.spyOn(clientMock, 'query');
+ poller.startPolling();
+ expect(clientMock.query).not.toHaveBeenCalled();
+ });
+
+ describe('when there are groups which have STARTED status', () => {
+ const TARGET_NAMESPACE = 'root';
+
+ const STARTED_GROUP_1 = {
+ status: STATUSES.STARTED,
+ id: 'started1',
+ import_target: {
+ target_namespace: TARGET_NAMESPACE,
+ new_name: 'group1',
+ },
+ };
+
+ const STARTED_GROUP_2 = {
+ status: STATUSES.STARTED,
+ id: 'started2',
+ import_target: {
+ target_namespace: TARGET_NAMESPACE,
+ new_name: 'group2',
+ },
+ };
+
+ const NOT_STARTED_GROUP = {
+ status: STATUSES.NONE,
+ id: 'not_started',
+ import_target: {
+ target_namespace: TARGET_NAMESPACE,
+ new_name: 'group3',
+ },
+ };
+
+ it('query server only for groups with STATUSES.STARTED', async () => {
+ clientMock.cache.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: {
+ bulkImportSourceGroups: [STARTED_GROUP_1, NOT_STARTED_GROUP, STARTED_GROUP_2].map(group =>
+ generateFakeEntry(group),
+ ),
+ },
+ });
+
+ clientMock.query = jest.fn().mockResolvedValue({ data: {} });
+ poller.startPolling();
+
+ expect(clientMock.query).toHaveBeenCalledTimes(1);
+ await waitForPromises();
+ const [[doc]] = clientMock.query.mock.calls;
+ const { selections } = doc.query.definitions[0].selectionSet;
+ expect(selections.every(field => field.name.value === 'group')).toBeTruthy();
+ expect(selections).toHaveLength(2);
+ expect(selections.map(sel => sel.arguments[0].value.value)).toStrictEqual([
+ `${TARGET_NAMESPACE}/${STARTED_GROUP_1.import_target.new_name}`,
+ `${TARGET_NAMESPACE}/${STARTED_GROUP_2.import_target.new_name}`,
+ ]);
+ });
+
+ it('updates statuses only for groups in response', async () => {
+ clientMock.cache.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: {
+ bulkImportSourceGroups: [STARTED_GROUP_1, STARTED_GROUP_2].map(group =>
+ generateFakeEntry(group),
+ ),
+ },
+ });
+
+ clientMock.query = jest.fn().mockResolvedValue({ data: { group0: {} } });
+ poller.startPolling();
+ await waitForPromises();
+ const [managerInstance] = SourceGroupsManager.mock.instances;
+ expect(managerInstance.setImportStatus).toHaveBeenCalledTimes(1);
+ expect(managerInstance.setImportStatus).toHaveBeenCalledWith(
+ expect.objectContaining({ id: STARTED_GROUP_1.id }),
+ STATUSES.FINISHED,
+ );
+ });
+
+ describe('when error occurs', () => {
+ beforeEach(() => {
+ clientMock.cache.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: {
+ bulkImportSourceGroups: [STARTED_GROUP_1, STARTED_GROUP_2].map(group =>
+ generateFakeEntry(group),
+ ),
+ },
+ });
+
+ clientMock.query = jest.fn().mockRejectedValue(new Error('dummy error'));
+ poller.startPolling();
+ return waitForPromises();
+ });
+
+ it('reports an error', () => {
+ expect(createFlash).toHaveBeenCalled();
+ });
+
+ it('continues polling', async () => {
+ jest.advanceTimersByTime(TEST_POLL_INTERVAL);
+ expect(listQueryCacheCalls()).toHaveLength(2);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/import_projects/components/bitbucket_status_table_spec.js b/spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js
index b65b388fd5f..8f8c01a8b81 100644
--- a/spec/frontend/import_projects/components/bitbucket_status_table_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js
@@ -2,8 +2,8 @@ import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import { GlAlert } from '@gitlab/ui';
-import BitbucketStatusTable from '~/import_projects/components/bitbucket_status_table.vue';
-import ImportProjectsTable from '~/import_projects/components/import_projects_table.vue';
+import BitbucketStatusTable from '~/import_entities/import_projects/components/bitbucket_status_table.vue';
+import ImportProjectsTable from '~/import_entities/import_projects/components/import_projects_table.vue';
const ImportProjectsTableStub = {
name: 'ImportProjectsTable',
diff --git a/spec/frontend/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
index 7322c7c1ae1..b4ac11b4404 100644
--- a/spec/frontend/import_projects/components/import_projects_table_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
@@ -2,11 +2,11 @@ import { nextTick } from 'vue';
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlButton, GlIntersectionObserver } from '@gitlab/ui';
-import state from '~/import_projects/store/state';
-import * as getters from '~/import_projects/store/getters';
-import { STATUSES } from '~/import_projects/constants';
-import ImportProjectsTable from '~/import_projects/components/import_projects_table.vue';
-import ProviderRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue';
+import state from '~/import_entities/import_projects/store/state';
+import * as getters from '~/import_entities/import_projects/store/getters';
+import { STATUSES } from '~/import_entities/constants';
+import ImportProjectsTable from '~/import_entities/import_projects/components/import_projects_table.vue';
+import ProviderRepoTableRow from '~/import_entities/import_projects/components/provider_repo_table_row.vue';
describe('ImportProjectsTable', () => {
let wrapper;
diff --git a/spec/frontend/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
index 03e30ef610e..aa003226050 100644
--- a/spec/frontend/import_projects/components/provider_repo_table_row_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
@@ -2,9 +2,9 @@ import { nextTick } from 'vue';
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlBadge } from '@gitlab/ui';
-import ProviderRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue';
-import ImportStatus from '~/import_projects/components/import_status.vue';
-import { STATUSES } from '~/import_projects/constants';
+import ProviderRepoTableRow from '~/import_entities/import_projects/components/provider_repo_table_row.vue';
+import ImportStatus from '~/import_entities/components/import_status.vue';
+import { STATUSES } from '~/import_entities//constants';
import Select2Select from '~/vue_shared/components/select2_select.vue';
describe('ProviderRepoTableRow', () => {
diff --git a/spec/frontend/import_projects/store/actions_spec.js b/spec/frontend/import_entities/import_projects/store/actions_spec.js
index 06afb20c6a2..5d4e73a17a3 100644
--- a/spec/frontend/import_projects/store/actions_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/actions_spec.js
@@ -17,11 +17,11 @@ import {
RECEIVE_NAMESPACES_ERROR,
SET_PAGE,
SET_FILTER,
-} from '~/import_projects/store/mutation_types';
-import actionsFactory from '~/import_projects/store/actions';
-import { getImportTarget } from '~/import_projects/store/getters';
-import state from '~/import_projects/store/state';
-import { STATUSES } from '~/import_projects/constants';
+} from '~/import_entities/import_projects/store/mutation_types';
+import actionsFactory from '~/import_entities/import_projects/store/actions';
+import { getImportTarget } from '~/import_entities/import_projects/store/getters';
+import state from '~/import_entities/import_projects/store/state';
+import { STATUSES } from '~/import_entities/constants';
jest.mock('~/flash');
diff --git a/spec/frontend/import_projects/store/getters_spec.js b/spec/frontend/import_entities/import_projects/store/getters_spec.js
index 1ce42e534ea..f0ccffc19f2 100644
--- a/spec/frontend/import_projects/store/getters_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/getters_spec.js
@@ -5,9 +5,9 @@ import {
hasImportableRepos,
importAllCount,
getImportTarget,
-} from '~/import_projects/store/getters';
-import { STATUSES } from '~/import_projects/constants';
-import state from '~/import_projects/store/state';
+} from '~/import_entities/import_projects/store/getters';
+import { STATUSES } from '~/import_entities/constants';
+import state from '~/import_entities/import_projects/store/state';
const IMPORTED_REPO = {
importSource: {},
diff --git a/spec/frontend/import_projects/store/mutations_spec.js b/spec/frontend/import_entities/import_projects/store/mutations_spec.js
index 5d78a7fa9e7..8b7ddffe6f4 100644
--- a/spec/frontend/import_projects/store/mutations_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/mutations_spec.js
@@ -1,7 +1,7 @@
-import * as types from '~/import_projects/store/mutation_types';
-import mutations from '~/import_projects/store/mutations';
-import getInitialState from '~/import_projects/store/state';
-import { STATUSES } from '~/import_projects/constants';
+import * as types from '~/import_entities/import_projects/store/mutation_types';
+import mutations from '~/import_entities/import_projects/store/mutations';
+import getInitialState from '~/import_entities/import_projects/store/state';
+import { STATUSES } from '~/import_entities/constants';
describe('import_projects store mutations', () => {
let state;
diff --git a/spec/frontend/import_projects/utils_spec.js b/spec/frontend/import_entities/import_projects/utils_spec.js
index 4e1e16a3184..7d9c4b7137e 100644
--- a/spec/frontend/import_projects/utils_spec.js
+++ b/spec/frontend/import_entities/import_projects/utils_spec.js
@@ -1,5 +1,9 @@
-import { isProjectImportable, isIncompatible, getImportStatus } from '~/import_projects/utils';
-import { STATUSES } from '~/import_projects/constants';
+import {
+ isProjectImportable,
+ isIncompatible,
+ getImportStatus,
+} from '~/import_entities/import_projects/utils';
+import { STATUSES } from '~/import_entities/constants';
describe('import_projects utils', () => {
const COMPATIBLE_PROJECT = {
diff --git a/spec/frontend/importer_status_spec.js b/spec/frontend/importer_status_spec.js
deleted file mode 100644
index 4ef74a2fe84..00000000000
--- a/spec/frontend/importer_status_spec.js
+++ /dev/null
@@ -1,141 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import { ImporterStatus } from '~/importer_status';
-import axios from '~/lib/utils/axios_utils';
-
-describe('Importer Status', () => {
- let instance;
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('addToImport', () => {
- const importUrl = '/import_url';
- const fixtures = `
- <table>
- <tr id="repo_123">
- <td class="import-target"></td>
- <td class="import-actions job-status">
- <button name="button" type="submit" class="btn btn-import js-add-to-import">
- </button>
- </td>
- </tr>
- </table>
- `;
-
- beforeEach(() => {
- setFixtures(fixtures);
- jest.spyOn(ImporterStatus.prototype, 'initStatusPage').mockImplementation(() => {});
- jest.spyOn(ImporterStatus.prototype, 'setAutoUpdate').mockImplementation(() => {});
- instance = new ImporterStatus({
- jobsUrl: '',
- importUrl,
- });
- });
-
- it('sets table row to active after post request', done => {
- mock.onPost(importUrl).reply(200, {
- id: 1,
- full_path: '/full_path',
- });
-
- instance
- .addToImport({
- currentTarget: document.querySelector('.js-add-to-import'),
- })
- .then(() => {
- expect(document.querySelector('tr').classList.contains('table-active')).toEqual(true);
- done();
- })
- .catch(done.fail);
- });
-
- it('shows error message after failed POST request', done => {
- setFixtures(`${fixtures}<div class="flash-container"></div>`);
-
- mock.onPost(importUrl).reply(422, {
- errors: 'You forgot your lunch',
- });
-
- instance
- .addToImport({
- currentTarget: document.querySelector('.js-add-to-import'),
- })
- .then(() => {
- const flashMessage = document.querySelector('.flash-text');
-
- expect(flashMessage.textContent.trim()).toEqual(
- 'An error occurred while importing project: You forgot your lunch',
- );
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('autoUpdate', () => {
- const jobsUrl = '/jobs_url';
-
- beforeEach(() => {
- const div = document.createElement('div');
- div.innerHTML = `
- <div id="project_1">
- <div class="job-status">
- </div>
- </div>
- `;
-
- document.body.appendChild(div);
-
- jest.spyOn(ImporterStatus.prototype, 'initStatusPage').mockImplementation(() => {});
- jest.spyOn(ImporterStatus.prototype, 'setAutoUpdate').mockImplementation(() => {});
- instance = new ImporterStatus({
- jobsUrl,
- });
- });
-
- function setupMock(importStatus) {
- mock.onGet(jobsUrl).reply(200, [
- {
- id: 1,
- import_status: importStatus,
- },
- ]);
- }
-
- function expectJobStatus(done, status) {
- instance
- .autoUpdate()
- .then(() => {
- expect(document.querySelector('#project_1').innerText.trim()).toEqual(status);
- done();
- })
- .catch(done.fail);
- }
-
- it('sets the job status to done', done => {
- setupMock('finished');
- expectJobStatus(done, 'Done');
- });
-
- it('sets the job status to scheduled', done => {
- setupMock('scheduled');
- expectJobStatus(done, 'Scheduled');
- });
-
- it('sets the job status to started', done => {
- setupMock('started');
- expectJobStatus(done, 'Started');
- });
-
- it('sets the job status to custom status', done => {
- setupMock('custom status');
- expectJobStatus(done, 'custom status');
- });
- });
-});
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
index e4620590e62..c3fd4a9bab2 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
@@ -17,7 +17,7 @@ exports[`Alert integration settings form default state should match the default
data-qa-selector="create_issue_checkbox"
>
<span>
- Create an issue. Issues are created for each alert triggered.
+ Create an incident. Incidents are created for each alert triggered.
</span>
</gl-form-checkbox-stub>
</gl-form-group-stub>
@@ -32,7 +32,7 @@ exports[`Alert integration settings form default state should match the default
for="alert-integration-settings-issue-template"
>
- Issue template (optional)
+ Incident template (optional)
<gl-link-stub
href="/help/user/project/description_templates#creating-issue-templates"
@@ -47,7 +47,7 @@ exports[`Alert integration settings form default state should match the default
<gl-dropdown-stub
block="true"
- category="tertiary"
+ category="primary"
data-qa-selector="incident_templates_dropdown"
headertext=""
id="alert-integration-settings-issue-template"
@@ -60,6 +60,7 @@ exports[`Alert integration settings form default state should match the default
data-qa-selector="incident_templates_item"
iconcolor=""
iconname=""
+ iconrightarialabel=""
iconrightname=""
ischeckitem="true"
secondarytext=""
@@ -88,7 +89,7 @@ exports[`Alert integration settings form default state should match the default
checked="true"
>
<span>
- Automatically close incident issues when the associated Prometheus alert resolves.
+ Automatically close incidents when the associated Prometheus alert resolves.
</span>
</gl-form-checkbox-stub>
</gl-form-group-stub>
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
index 072e611b9a4..2b3c803be08 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
@@ -43,6 +43,7 @@ exports[`IncidentsSettingTabs should render the component 1`] = `
>
<gl-tab-stub
title="Alert integration"
+ titlelinkclass=""
>
<alertssettingsform-stub
class="gl-pt-3"
@@ -51,6 +52,7 @@ exports[`IncidentsSettingTabs should render the component 1`] = `
</gl-tab-stub>
<gl-tab-stub
title="PagerDuty integration"
+ titlelinkclass=""
>
<pagerdutysettingsform-stub
class="gl-pt-3"
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
index 273356151fc..f0eb54c1b3a 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
@@ -5,7 +5,9 @@ exports[`Alert integration settings form should match the default snapshot 1`] =
<!---->
<p>
- Setting up a webhook with PagerDuty will automatically create a GitLab issue for each PagerDuty incident.
+ <gl-sprintf-stub
+ message="Create a GitLab incident for each PagerDuty incident by %{linkStart}configuring a webhook in PagerDuty%{linkEnd}"
+ />
</p>
<form>
@@ -33,18 +35,10 @@ exports[`Alert integration settings form should match the default snapshot 1`] =
value="pagerduty.webhook.com"
/>
- <div
- class="gl-text-gray-200 gl-pt-2"
- >
- <gl-sprintf-stub
- message="Create a GitLab issue for each PagerDuty incident by %{docsLink}"
- />
- </div>
-
<gl-button-stub
buttontextclasses=""
category="primary"
- class="gl-mt-3"
+ class="gl-mt-5"
data-testid="webhook-reset-btn"
icon=""
role="button"
diff --git a/spec/frontend/integrations/edit/store/actions_spec.js b/spec/frontend/integrations/edit/store/actions_spec.js
index 5b5c8d6f76e..1ff881c265d 100644
--- a/spec/frontend/integrations/edit/store/actions_spec.js
+++ b/spec/frontend/integrations/edit/store/actions_spec.js
@@ -1,13 +1,19 @@
import testAction from 'helpers/vuex_action_helper';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
import createState from '~/integrations/edit/store/state';
import {
setOverride,
setIsSaving,
setIsTesting,
setIsResetting,
+ requestResetIntegration,
+ receiveResetIntegrationSuccess,
+ receiveResetIntegrationError,
} from '~/integrations/edit/store/actions';
import * as types from '~/integrations/edit/store/mutation_types';
+jest.mock('~/lib/utils/url_utility');
+
describe('Integration form store actions', () => {
let state;
@@ -40,4 +46,28 @@ describe('Integration form store actions', () => {
]);
});
});
+
+ describe('requestResetIntegration', () => {
+ it('should commit REQUEST_RESET_INTEGRATION mutation', () => {
+ return testAction(requestResetIntegration, null, state, [
+ { type: types.REQUEST_RESET_INTEGRATION },
+ ]);
+ });
+ });
+
+ describe('receiveResetIntegrationSuccess', () => {
+ it('should call refreshCurrentPage()', () => {
+ return testAction(receiveResetIntegrationSuccess, null, state, [], [], () => {
+ expect(refreshCurrentPage).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('receiveResetIntegrationError', () => {
+ it('should commit RECEIVE_RESET_INTEGRATION_ERROR mutation', () => {
+ return testAction(receiveResetIntegrationError, null, state, [
+ { type: types.RECEIVE_RESET_INTEGRATION_ERROR },
+ ]);
+ });
+ });
});
diff --git a/spec/frontend/integrations/edit/store/mutations_spec.js b/spec/frontend/integrations/edit/store/mutations_spec.js
index 4707b4b3714..81f39adb87f 100644
--- a/spec/frontend/integrations/edit/store/mutations_spec.js
+++ b/spec/frontend/integrations/edit/store/mutations_spec.js
@@ -40,4 +40,20 @@ describe('Integration form store mutations', () => {
expect(state.isResetting).toBe(true);
});
});
+
+ describe(`${types.REQUEST_RESET_INTEGRATION}`, () => {
+ it('sets isResetting', () => {
+ mutations[types.REQUEST_RESET_INTEGRATION](state);
+
+ expect(state.isResetting).toBe(true);
+ });
+ });
+
+ describe(`${types.RECEIVE_RESET_INTEGRATION_ERROR}`, () => {
+ it('sets isResetting', () => {
+ mutations[types.RECEIVE_RESET_INTEGRATION_ERROR](state);
+
+ expect(state.isResetting).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/invite_members/components/members_token_select_spec.js b/spec/frontend/invite_members/components/members_token_select_spec.js
index fb0bd6bb195..106a2df783d 100644
--- a/spec/frontend/invite_members/components/members_token_select_spec.js
+++ b/spec/frontend/invite_members/components/members_token_select_spec.js
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { GlTokenSelector } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
+import { stubComponent } from 'helpers/stub_component';
import Api from '~/api';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
@@ -17,6 +18,9 @@ const createComponent = () => {
ariaLabelledby: label,
placeholder,
},
+ stubs: {
+ GlTokenSelector: stubComponent(GlTokenSelector),
+ },
});
};
diff --git a/spec/frontend/issuable/related_issues/components/issue_token_spec.js b/spec/frontend/issuable/related_issues/components/issue_token_spec.js
index 1b4c6b548e2..d5181d4a17a 100644
--- a/spec/frontend/issuable/related_issues/components/issue_token_spec.js
+++ b/spec/frontend/issuable/related_issues/components/issue_token_spec.js
@@ -100,7 +100,7 @@ describe('IssueToken', () => {
state,
});
- expect(findReferenceIcon().attributes('aria-label')).toBe(state);
+ expect(findReferenceIcon().props('ariaLabel')).toBe(state);
expect(findReference().text()).toBe(displayReference);
expect(findTitle().text()).toBe(title);
});
diff --git a/spec/frontend/issuable/related_issues/components/related_issues_root_spec.js b/spec/frontend/issuable/related_issues/components/related_issues_root_spec.js
index 2544d0bd030..2c02e1e1de4 100644
--- a/spec/frontend/issuable/related_issues/components/related_issues_root_spec.js
+++ b/spec/frontend/issuable/related_issues/components/related_issues_root_spec.js
@@ -280,7 +280,7 @@ describe('RelatedIssuesRoot', () => {
const input = 'asdf/qwer#444 #12 ';
wrapper.vm.onInput({
untouchedRawReferences: input.trim().split(/\s/),
- touchedReference: 2,
+ touchedReference: '2',
});
expect(wrapper.vm.state.pendingReferences).toHaveLength(2);
@@ -292,13 +292,37 @@ describe('RelatedIssuesRoot', () => {
const input = 'something random ';
wrapper.vm.onInput({
untouchedRawReferences: input.trim().split(/\s/),
- touchedReference: 2,
+ touchedReference: '2',
});
expect(wrapper.vm.state.pendingReferences).toHaveLength(2);
expect(wrapper.vm.state.pendingReferences[0]).toEqual('something');
expect(wrapper.vm.state.pendingReferences[1]).toEqual('random');
});
+
+ it('prepends # when user enters a numeric value [0-9]', async () => {
+ const input = '23';
+
+ wrapper.vm.onInput({
+ untouchedRawReferences: input.trim().split(/\s/),
+ touchedReference: input,
+ });
+
+ expect(wrapper.vm.inputValue).toBe(`#${input}`);
+ });
+
+ it('prepends # when user enters a number', async () => {
+ const input = 23;
+
+ wrapper.vm.onInput({
+ untouchedRawReferences: String(input)
+ .trim()
+ .split(/\s/),
+ touchedReference: input,
+ });
+
+ expect(wrapper.vm.inputValue).toBe(`#${input}`);
+ });
});
describe('onBlur', () => {
diff --git a/spec/frontend/issuable_show/components/issuable_body_spec.js b/spec/frontend/issuable_show/components/issuable_body_spec.js
index 0e4475e8103..5708eaf4a31 100644
--- a/spec/frontend/issuable_show/components/issuable_body_spec.js
+++ b/spec/frontend/issuable_show/components/issuable_body_spec.js
@@ -135,6 +135,33 @@ describe('IssuableBody', () => {
expect(wrapper.emitted('edit-issuable')).toBeTruthy();
});
+
+ it.each(['keydown-title', 'keydown-description'])(
+ 'component emits `%s` event with event object and issuableMeta params via issuable-edit-form',
+ async eventName => {
+ const eventObj = {
+ preventDefault: jest.fn(),
+ stopPropagation: jest.fn(),
+ };
+ const issuableMeta = {
+ issuableTitle: 'foo',
+ issuableDescription: 'foobar',
+ };
+
+ wrapper.setProps({
+ editFormVisible: true,
+ });
+
+ await wrapper.vm.$nextTick();
+
+ const issuableEditForm = wrapper.find(IssuableEditForm);
+
+ issuableEditForm.vm.$emit(eventName, eventObj, issuableMeta);
+
+ expect(wrapper.emitted(eventName)).toBeTruthy();
+ expect(wrapper.emitted(eventName)[0]).toMatchObject([eventObj, issuableMeta]);
+ },
+ );
});
});
});
diff --git a/spec/frontend/issuable_show/components/issuable_edit_form_spec.js b/spec/frontend/issuable_show/components/issuable_edit_form_spec.js
index 352e66cdffe..a865bdb5608 100644
--- a/spec/frontend/issuable_show/components/issuable_edit_form_spec.js
+++ b/spec/frontend/issuable_show/components/issuable_edit_form_spec.js
@@ -41,6 +41,40 @@ describe('IssuableEditForm', () => {
wrapper.destroy();
});
+ describe('watch', () => {
+ describe('issuable', () => {
+ it('sets title and description to `issuable.title` and `issuable.description` when those values are available', async () => {
+ wrapper.setProps({
+ issuable: {
+ ...issuableEditFormProps.issuable,
+ title: 'Foo',
+ description: 'Foobar',
+ },
+ });
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.title).toBe('Foo');
+ expect(wrapper.vm.description).toBe('Foobar');
+ });
+
+ it('sets title and description to empty string when `issuable.title` and `issuable.description` is unavailable', async () => {
+ wrapper.setProps({
+ issuable: {
+ ...issuableEditFormProps.issuable,
+ title: null,
+ description: null,
+ },
+ });
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.title).toBe('');
+ expect(wrapper.vm.description).toBe('');
+ });
+ });
+ });
+
describe('created', () => {
it('binds `update.issuable` and `close.form` event listeners', () => {
const eventOnSpy = jest.spyOn(IssuableEventHub, '$on');
@@ -118,5 +152,42 @@ describe('IssuableEditForm', () => {
expect(actionsEl.find('button.js-save').exists()).toBe(true);
expect(actionsEl.find('button.js-cancel').exists()).toBe(true);
});
+
+ describe('events', () => {
+ const eventObj = {
+ preventDefault: jest.fn(),
+ stopPropagation: jest.fn(),
+ };
+
+ it('component emits `keydown-title` event with event object and issuableMeta params via gl-form-input', async () => {
+ const titleInputEl = wrapper.find(GlFormInput);
+
+ titleInputEl.vm.$emit('keydown', eventObj, 'title');
+
+ expect(wrapper.emitted('keydown-title')).toBeTruthy();
+ expect(wrapper.emitted('keydown-title')[0]).toMatchObject([
+ eventObj,
+ {
+ issuableTitle: wrapper.vm.title,
+ issuableDescription: wrapper.vm.description,
+ },
+ ]);
+ });
+
+ it('component emits `keydown-description` event with event object and issuableMeta params via textarea', async () => {
+ const descriptionInputEl = wrapper.find('[data-testid="description"] textarea');
+
+ descriptionInputEl.trigger('keydown', eventObj, 'description');
+
+ expect(wrapper.emitted('keydown-description')).toBeTruthy();
+ expect(wrapper.emitted('keydown-description')[0]).toMatchObject([
+ eventObj,
+ {
+ issuableTitle: wrapper.vm.title,
+ issuableDescription: wrapper.vm.description,
+ },
+ ]);
+ });
+ });
});
});
diff --git a/spec/frontend/issuable_show/components/issuable_show_root_spec.js b/spec/frontend/issuable_show/components/issuable_show_root_spec.js
index 112e4ccd340..ca0aefc1083 100644
--- a/spec/frontend/issuable_show/components/issuable_show_root_spec.js
+++ b/spec/frontend/issuable_show/components/issuable_show_root_spec.js
@@ -118,6 +118,27 @@ describe('IssuableShowRoot', () => {
expect(wrapper.emitted('sidebar-toggle')).toBeTruthy();
});
+
+ it.each(['keydown-title', 'keydown-description'])(
+ 'component emits `%s` event with event object and issuableMeta params via issuable-body',
+ eventName => {
+ const eventObj = {
+ preventDefault: jest.fn(),
+ stopPropagation: jest.fn(),
+ };
+ const issuableMeta = {
+ issuableTitle: 'foo',
+ issuableDescription: 'foobar',
+ };
+
+ const issuableBody = wrapper.find(IssuableBody);
+
+ issuableBody.vm.$emit(eventName, eventObj, issuableMeta);
+
+ expect(wrapper.emitted(eventName)).toBeTruthy();
+ expect(wrapper.emitted(eventName)[0]).toMatchObject([eventObj, issuableMeta]);
+ },
+ );
});
});
});
diff --git a/spec/frontend/issuable_show/mock_data.js b/spec/frontend/issuable_show/mock_data.js
index 14e5febdc6b..af854f420bc 100644
--- a/spec/frontend/issuable_show/mock_data.js
+++ b/spec/frontend/issuable_show/mock_data.js
@@ -28,7 +28,9 @@ export const mockIssuableShowProps = {
descriptionPreviewPath: '/gitlab-org/gitlab-shell/preview_markdown',
editFormVisible: false,
enableAutocomplete: true,
+ enableAutosave: true,
enableEdit: true,
+ showFieldTitle: false,
statusBadgeClass: 'status-box-open',
statusIcon: 'issue-open-m',
};
diff --git a/spec/frontend/issue_show/components/header_actions_spec.js b/spec/frontend/issue_show/components/header_actions_spec.js
index 67b8665a889..b9836ae7240 100644
--- a/spec/frontend/issue_show/components/header_actions_spec.js
+++ b/spec/frontend/issue_show/components/header_actions_spec.js
@@ -7,6 +7,7 @@ import HeaderActions from '~/issue_show/components/header_actions.vue';
import { IssuableStatus, IssueStateEvent } from '~/issue_show/constants';
import promoteToEpicMutation from '~/issue_show/queries/promote_to_epic.mutation.graphql';
import * as urlUtility from '~/lib/utils/url_utility';
+import eventHub from '~/notes/event_hub';
import createStore from '~/notes/stores';
jest.mock('~/flash');
@@ -82,8 +83,10 @@ describe('HeaderActions component', () => {
} = {}) => {
mutateMock = jest.fn().mockResolvedValue(mutateResponse);
- store.getters.getNoteableData.state = issueState;
- store.getters.getNoteableData.blocked_by_issues = blockedByIssues;
+ store.dispatch('setNoteableData', {
+ blocked_by_issues: blockedByIssues,
+ state: issueState,
+ });
return shallowMount(HeaderActions, {
localVue,
@@ -273,6 +276,26 @@ describe('HeaderActions component', () => {
});
});
+ describe('when `toggle.issuable.state` event is emitted', () => {
+ it('invokes a method to toggle the issue state', () => {
+ wrapper = mountComponent({ mutateResponse: updateIssueMutationResponse });
+
+ eventHub.$emit('toggle.issuable.state');
+
+ expect(mutateMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ variables: {
+ input: {
+ iid: defaultProps.iid,
+ projectPath: defaultProps.projectPath,
+ stateEvent: IssueStateEvent.Close,
+ },
+ },
+ }),
+ );
+ });
+ });
+
describe('modal', () => {
const blockedByIssues = [
{ iid: 13, web_url: 'gitlab-org/gitlab-test/-/issues/13' },
diff --git a/spec/frontend/issue_show/issue_spec.js b/spec/frontend/issue_show/issue_spec.js
index 7a48353af94..cee9969d26a 100644
--- a/spec/frontend/issue_show/issue_spec.js
+++ b/spec/frontend/issue_show/issue_spec.js
@@ -30,7 +30,8 @@ describe('Issue show index', () => {
initialDescriptionHtml: '<svg onload=window.alert(1)>',
});
- const issuableData = parseData.parseIssuableData();
+ const initialDataEl = document.getElementById('js-issuable-app');
+ const issuableData = parseData.parseIssuableData(initialDataEl);
initIssuableApp(issuableData, createStore());
await waitForPromises();
diff --git a/spec/frontend/issue_spec.js b/spec/frontend/issue_spec.js
index 24020daf728..00595736821 100644
--- a/spec/frontend/issue_spec.js
+++ b/spec/frontend/issue_spec.js
@@ -1,5 +1,3 @@
-/* eslint-disable one-var, no-use-before-define */
-
import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
@@ -7,59 +5,16 @@ import Issue from '~/issue';
import '~/lib/utils/text_utility';
describe('Issue', () => {
+ let $boxClosed;
+ let $boxOpen;
let testContext;
beforeEach(() => {
testContext = {};
});
- let $boxClosed, $boxOpen, $btn;
-
preloadFixtures('issues/closed-issue.html');
- preloadFixtures('issues/issue-with-task-list.html');
preloadFixtures('issues/open-issue.html');
- preloadFixtures('static/issue_with_mermaid_graph.html');
-
- function expectErrorMessage() {
- const $flashMessage = $('div.flash-alert');
-
- expect($flashMessage).toExist();
- expect($flashMessage).toBeVisible();
- expect($flashMessage).toHaveText('Unable to update this issue at this time.');
- }
-
- function expectIssueState(isIssueOpen) {
- expectVisibility($boxClosed, !isIssueOpen);
- expectVisibility($boxOpen, isIssueOpen);
-
- expect($btn).toHaveText(isIssueOpen ? 'Close issue' : 'Reopen issue');
- }
-
- function expectNewBranchButtonState(isPending, canCreate) {
- if (Issue.$btnNewBranch.length === 0) {
- return;
- }
-
- const $available = Issue.$btnNewBranch.find('.available');
-
- expect($available).toHaveText('New branch');
-
- if (!isPending && canCreate) {
- expect($available).toBeVisible();
- } else {
- expect($available).toBeHidden();
- }
-
- const $unavailable = Issue.$btnNewBranch.find('.unavailable');
-
- expect($unavailable).toHaveText('New branch unavailable');
-
- if (!isPending && !canCreate) {
- expect($unavailable).toBeVisible();
- } else {
- expect($unavailable).toBeHidden();
- }
- }
function expectVisibility($element, shouldBeVisible) {
if (shouldBeVisible) {
@@ -69,7 +24,12 @@ describe('Issue', () => {
}
}
- function findElements(isIssueInitiallyOpen) {
+ function expectIssueState(isIssueOpen) {
+ expectVisibility($boxClosed, !isIssueOpen);
+ expectVisibility($boxOpen, isIssueOpen);
+ }
+
+ function findElements() {
$boxClosed = $('div.status-box-issue-closed');
expect($boxClosed).toExist();
@@ -79,11 +39,6 @@ describe('Issue', () => {
expect($boxOpen).toExist();
expect($boxOpen).toHaveText('Open');
-
- $btn = $('.js-issuable-close-button');
-
- expect($btn).toExist();
- expect($btn).toHaveText(isIssueInitiallyOpen ? 'Close issue' : 'Reopen issue');
}
[true, false].forEach(isIssueInitiallyOpen => {
@@ -99,25 +54,6 @@ describe('Issue', () => {
testContext.$projectIssuesCounter.text('1,001');
}
- function mockCloseButtonResponseSuccess(url, response) {
- mock.onPut(url).reply(() => {
- expectNewBranchButtonState(true, false);
-
- return [200, response];
- });
- }
-
- function mockCloseButtonResponseError(url) {
- mock.onPut(url).networkError();
- }
-
- function mockCanCreateBranch(canCreateBranch) {
- mock.onGet(/(.*)\/can_create_branch$/).reply(200, {
- can_create_branch: canCreateBranch,
- suggested_branch_name: 'foo-99',
- });
- }
-
beforeEach(() => {
if (isIssueInitiallyOpen) {
loadFixtures('issues/open-issue.html');
@@ -130,7 +66,6 @@ describe('Issue', () => {
jest.spyOn(axios, 'get');
findElements(isIssueInitiallyOpen);
- testContext.$triggeredButton = $btn;
});
afterEach(() => {
@@ -138,120 +73,19 @@ describe('Issue', () => {
$('div.flash-alert').remove();
});
- it(`${action}s the issue`, done => {
- mockCloseButtonResponseSuccess(testContext.$triggeredButton.attr('href'), {
- id: 34,
- });
- mockCanCreateBranch(!isIssueInitiallyOpen);
-
- setup();
- testContext.$triggeredButton.trigger('click');
-
- setImmediate(() => {
- expectIssueState(!isIssueInitiallyOpen);
-
- expect(testContext.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
- expect(testContext.$projectIssuesCounter.text()).toBe(
- isIssueInitiallyOpen ? '1,000' : '1,002',
- );
- expectNewBranchButtonState(false, !isIssueInitiallyOpen);
-
- done();
- });
- });
-
- it(`fails to ${action} the issue if saved:false`, done => {
- mockCloseButtonResponseSuccess(testContext.$triggeredButton.attr('href'), {
- saved: false,
- });
- mockCanCreateBranch(isIssueInitiallyOpen);
-
- setup();
- testContext.$triggeredButton.trigger('click');
-
- setImmediate(() => {
- expectIssueState(isIssueInitiallyOpen);
-
- expect(testContext.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
- expectErrorMessage();
-
- expect(testContext.$projectIssuesCounter.text()).toBe('1,001');
- expectNewBranchButtonState(false, isIssueInitiallyOpen);
-
- done();
- });
- });
-
- it(`fails to ${action} the issue if HTTP error occurs`, done => {
- mockCloseButtonResponseError(testContext.$triggeredButton.attr('href'));
- mockCanCreateBranch(isIssueInitiallyOpen);
-
- setup();
- testContext.$triggeredButton.trigger('click');
-
- setImmediate(() => {
- expectIssueState(isIssueInitiallyOpen);
-
- expect(testContext.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
- expectErrorMessage();
-
- expect(testContext.$projectIssuesCounter.text()).toBe('1,001');
- expectNewBranchButtonState(false, isIssueInitiallyOpen);
-
- done();
- });
- });
-
- it('disables the new branch button if Ajax call fails', () => {
- mockCloseButtonResponseError(testContext.$triggeredButton.attr('href'));
- mock.onGet(/(.*)\/can_create_branch$/).networkError();
-
- setup();
- testContext.$triggeredButton.trigger('click');
-
- expectNewBranchButtonState(false, false);
- });
-
- it('does not trigger Ajax call if new branch button is missing', done => {
- mockCloseButtonResponseError(testContext.$triggeredButton.attr('href'));
-
- document.querySelector('#related-branches').remove();
- document.querySelector('.create-mr-dropdown-wrap').remove();
-
+ it(`${action}s the issue on dispatch of issuable_vue_app:change event`, () => {
setup();
- testContext.$triggeredButton.trigger('click');
-
- setImmediate(() => {
- expect(axios.get).not.toHaveBeenCalled();
-
- done();
- });
- });
- });
- });
-
- describe('when not displaying blocked warning', () => {
- describe('when clicking a mermaid graph inside an issue description', () => {
- let mock;
- let spy;
-
- beforeEach(() => {
- loadFixtures('static/issue_with_mermaid_graph.html');
- mock = new MockAdapter(axios);
- spy = jest.spyOn(axios, 'put');
- });
-
- afterEach(() => {
- mock.restore();
- jest.clearAllMocks();
- });
-
- it('does not make a PUT request', () => {
- Issue.prototype.initIssueBtnEventListeners();
- $('svg a.js-issuable-actions').trigger('click');
+ document.dispatchEvent(
+ new CustomEvent('issuable_vue_app:change', {
+ detail: {
+ data: { id: 1 },
+ isClosed: isIssueInitiallyOpen,
+ },
+ }),
+ );
- expect(spy).not.toHaveBeenCalled();
+ expectIssueState(!isIssueInitiallyOpen);
});
});
});
diff --git a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
index fe6d9a34078..c40b7c90c72 100644
--- a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
+++ b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
@@ -120,6 +120,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] =
class="gl-search-box-by-type"
>
<svg
+ aria-hidden="true"
class="gl-search-box-by-type-search-icon gl-icon s16"
data-testid="search-icon"
>
@@ -234,6 +235,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] =
class="gl-search-box-by-type"
>
<svg
+ aria-hidden="true"
class="gl-search-box-by-type-search-icon gl-icon s16"
data-testid="search-icon"
>
diff --git a/spec/frontend/jobs/components/log/line_spec.js b/spec/frontend/jobs/components/log/line_spec.js
index 314b23ec29b..914ae2424c8 100644
--- a/spec/frontend/jobs/components/log/line_spec.js
+++ b/spec/frontend/jobs/components/log/line_spec.js
@@ -4,6 +4,7 @@ import LineNumber from '~/jobs/components/log/line_number.vue';
const httpUrl = 'http://example.com';
const httpsUrl = 'https://example.com';
+const queryUrl = 'https://example.com?param=val';
const mockProps = ({ text = 'Running with gitlab-runner 12.1.0 (de7731dd)' } = {}) => ({
line: {
@@ -21,7 +22,6 @@ const mockProps = ({ text = 'Running with gitlab-runner 12.1.0 (de7731dd)' } = {
describe('Job Log Line', () => {
let wrapper;
let data;
- let originalGon;
const createComponent = (props = {}) => {
wrapper = shallowMount(Line, {
@@ -33,25 +33,17 @@ describe('Job Log Line', () => {
const findLine = () => wrapper.find('span');
const findLink = () => findLine().find('a');
- const findLinksAt = i =>
- findLine()
- .findAll('a')
- .at(i);
+ const findLinks = () => findLine().findAll('a');
+ const findLinkAttributeByIndex = i =>
+ findLinks()
+ .at(i)
+ .attributes();
beforeEach(() => {
- originalGon = window.gon;
- window.gon.features = {
- ciJobLineLinks: false,
- };
-
data = mockProps();
createComponent(data);
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('renders the line number component', () => {
expect(wrapper.find(LineNumber).exists()).toBe(true);
});
@@ -64,44 +56,7 @@ describe('Job Log Line', () => {
expect(findLine().classes()).toContain(data.line.content[0].style);
});
- describe.each([true, false])('when feature ci_job_line_links enabled = %p', ciJobLineLinks => {
- beforeEach(() => {
- window.gon.features = {
- ciJobLineLinks,
- };
- });
-
- it('renders text with symbols', () => {
- const text = 'apt-get update < /dev/null > /dev/null';
- createComponent(mockProps({ text }));
-
- expect(findLine().text()).toBe(text);
- });
-
- it.each`
- tag | text
- ${'a'} | ${'<a href="#">linked</a>'}
- ${'script'} | ${'<script>doEvil();</script>'}
- ${'strong'} | ${'<strong>highlighted</strong>'}
- `('escapes `<$tag>` tags in text', ({ tag, text }) => {
- createComponent(mockProps({ text }));
-
- expect(
- findLine()
- .find(tag)
- .exists(),
- ).toBe(false);
- expect(findLine().text()).toBe(text);
- });
- });
-
- describe('when ci_job_line_links is enabled', () => {
- beforeEach(() => {
- window.gon.features = {
- ciJobLineLinks: true,
- };
- });
-
+ describe('job urls as links', () => {
it('renders an http link', () => {
createComponent(mockProps({ text: httpUrl }));
@@ -116,15 +71,6 @@ describe('Job Log Line', () => {
expect(findLink().attributes().href).toBe(httpsUrl);
});
- it('renders a multiple links surrounded by text', () => {
- createComponent(mockProps({ text: `My HTTP url: ${httpUrl} and my HTTPS url: ${httpsUrl}` }));
- expect(findLine().text()).toBe(
- 'My HTTP url: http://example.com and my HTTPS url: https://example.com',
- );
- expect(findLinksAt(0).attributes().href).toBe(httpUrl);
- expect(findLinksAt(1).attributes().href).toBe(httpsUrl);
- });
-
it('renders a link with rel nofollow and noopener', () => {
createComponent(mockProps({ text: httpsUrl }));
@@ -137,26 +83,70 @@ describe('Job Log Line', () => {
expect(findLink().classes()).toEqual(['gl-reset-color!', 'gl-text-decoration-underline']);
});
- it('render links surrounded by text', () => {
+ it('renders links with queries, surrounded by questions marks', () => {
+ createComponent(mockProps({ text: `Did you see my url ${queryUrl}??` }));
+
+ expect(findLine().text()).toBe('Did you see my url https://example.com?param=val??');
+ expect(findLinkAttributeByIndex(0).href).toBe(queryUrl);
+ });
+
+ it('renders links with queries, surrounded by exclamation marks', () => {
+ createComponent(mockProps({ text: `No! The ${queryUrl}!?` }));
+
+ expect(findLine().text()).toBe('No! The https://example.com?param=val!?');
+ expect(findLinkAttributeByIndex(0).href).toBe(queryUrl);
+ });
+
+ it('renders multiple links surrounded by text', () => {
createComponent(
- mockProps({ text: `My HTTP url: ${httpUrl} and my HTTPS url: ${httpsUrl} are here.` }),
+ mockProps({ text: `Well, my HTTP url: ${httpUrl} and my HTTPS url: ${httpsUrl}` }),
);
expect(findLine().text()).toBe(
- 'My HTTP url: http://example.com and my HTTPS url: https://example.com are here.',
+ 'Well, my HTTP url: http://example.com and my HTTPS url: https://example.com',
);
- expect(findLinksAt(0).attributes().href).toBe(httpUrl);
- expect(findLinksAt(1).attributes().href).toBe(httpsUrl);
+
+ expect(findLinks()).toHaveLength(2);
+
+ expect(findLinkAttributeByIndex(0).href).toBe(httpUrl);
+ expect(findLinkAttributeByIndex(1).href).toBe(httpsUrl);
+ });
+
+ it('renders multiple links surrounded by text, with other symbols', () => {
+ createComponent(
+ mockProps({ text: `${httpUrl}, ${httpUrl}: ${httpsUrl}; ${httpsUrl}. ${httpsUrl}...` }),
+ );
+ expect(findLine().text()).toBe(
+ 'http://example.com, http://example.com: https://example.com; https://example.com. https://example.com...',
+ );
+
+ expect(findLinks()).toHaveLength(5);
+
+ expect(findLinkAttributeByIndex(0).href).toBe(httpUrl);
+ expect(findLinkAttributeByIndex(1).href).toBe(httpUrl);
+ expect(findLinkAttributeByIndex(2).href).toBe(httpsUrl);
+ expect(findLinkAttributeByIndex(3).href).toBe(httpsUrl);
+ expect(findLinkAttributeByIndex(4).href).toBe(httpsUrl);
+ });
+
+ it('renders text with symbols in it', () => {
+ const text = 'apt-get update < /dev/null > /dev/null';
+ createComponent(mockProps({ text }));
+
+ expect(findLine().text()).toBe(text);
});
const jshref = 'javascript:doEvil();'; // eslint-disable-line no-script-url
- test.each`
- type | text
- ${'js'} | ${jshref}
- ${'file'} | ${'file:///a-file'}
- ${'ftp'} | ${'ftp://example.com/file'}
- ${'email'} | ${'email@example.com'}
- ${'no scheme'} | ${'example.com/page'}
+ it.each`
+ type | text
+ ${'html link'} | ${'<a href="#">linked</a>'}
+ ${'html script'} | ${'<script>doEvil();</script>'}
+ ${'html strong'} | ${'<strong>highlighted</strong>'}
+ ${'js'} | ${jshref}
+ ${'file'} | ${'file:///a-file'}
+ ${'ftp'} | ${'ftp://example.com/file'}
+ ${'email'} | ${'email@example.com'}
+ ${'no scheme'} | ${'example.com/page'}
`('does not render a $type link', ({ text }) => {
createComponent(mockProps({ text }));
expect(findLink().exists()).toBe(false);
diff --git a/spec/frontend/jobs/components/unmet_prerequisites_block_spec.js b/spec/frontend/jobs/components/unmet_prerequisites_block_spec.js
index 68fcb321214..9092d3f8163 100644
--- a/spec/frontend/jobs/components/unmet_prerequisites_block_spec.js
+++ b/spec/frontend/jobs/components/unmet_prerequisites_block_spec.js
@@ -1,37 +1,41 @@
-import Vue from 'vue';
-import component from '~/jobs/components/unmet_prerequisites_block.vue';
-import mountComponent from '../../helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
+import { GlAlert, GlLink } from '@gitlab/ui';
+import UnmetPrerequisitesBlock from '~/jobs/components/unmet_prerequisites_block.vue';
describe('Unmet Prerequisites Block Job component', () => {
- const Component = Vue.extend(component);
- let vm;
+ let wrapper;
const helpPath = '/user/project/clusters/index.html#troubleshooting-failed-deployment-jobs';
- beforeEach(() => {
- vm = mountComponent(Component, {
- hasNoRunnersForProject: true,
- helpPath,
+ const createComponent = () => {
+ wrapper = shallowMount(UnmetPrerequisitesBlock, {
+ propsData: {
+ helpPath,
+ },
});
+ };
+
+ beforeEach(() => {
+ createComponent();
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
it('renders an alert with the correct message', () => {
- const container = vm.$el.querySelector('.js-failed-unmet-prerequisites');
+ const container = wrapper.find(GlAlert);
const alertMessage =
'This job failed because the necessary resources were not successfully created.';
expect(container).not.toBeNull();
- expect(container.innerHTML).toContain(alertMessage);
+ expect(container.text()).toContain(alertMessage);
});
it('renders link to help page', () => {
- const helpLink = vm.$el.querySelector('.js-help-path');
+ const helpLink = wrapper.find(GlLink);
expect(helpLink).not.toBeNull();
- expect(helpLink.innerHTML).toContain('More information');
- expect(helpLink.getAttribute('href')).toEqual(helpPath);
+ expect(helpLink.text()).toContain('More information');
+ expect(helpLink.attributes().href).toEqual(helpPath);
});
});
diff --git a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
index 2f7a6030650..2175610b7a6 100644
--- a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
+++ b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
@@ -44,34 +44,84 @@ describe('DelayedJobMixin', () => {
});
});
- describe('if job is delayed job', () => {
- let remainingTimeInMilliseconds = 42000;
+ describe('in REST component', () => {
+ describe('if job is delayed job', () => {
+ let remainingTimeInMilliseconds = 42000;
- beforeEach(() => {
- jest
- .spyOn(Date, 'now')
- .mockImplementation(
- () => new Date(delayedJobFixture.scheduled_at).getTime() - remainingTimeInMilliseconds,
- );
+ beforeEach(() => {
+ jest
+ .spyOn(Date, 'now')
+ .mockImplementation(
+ () => new Date(delayedJobFixture.scheduled_at).getTime() - remainingTimeInMilliseconds,
+ );
- vm = mountComponent(dummyComponent, {
- job: delayedJobFixture,
+ vm = mountComponent(dummyComponent, {
+ job: delayedJobFixture,
+ });
+ });
+
+ describe('after mounting', () => {
+ beforeEach(() => vm.$nextTick());
+
+ it('sets remaining time', () => {
+ expect(vm.$el.innerText).toBe('00:00:42');
+ });
+
+ it('updates remaining time', () => {
+ remainingTimeInMilliseconds = 41000;
+ jest.advanceTimersByTime(1000);
+
+ return vm.$nextTick().then(() => {
+ expect(vm.$el.innerText).toBe('00:00:41');
+ });
+ });
});
});
+ });
- describe('after mounting', () => {
- beforeEach(() => vm.$nextTick());
+ describe('in GraphQL component', () => {
+ const mockGraphQlJob = {
+ name: 'build_b',
+ scheduledAt: new Date(delayedJobFixture.scheduled_at),
+ status: {
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/jobs/1515',
+ group: 'success',
+ action: null,
+ },
+ };
+
+ describe('if job is delayed job', () => {
+ let remainingTimeInMilliseconds = 42000;
- it('sets remaining time', () => {
- expect(vm.$el.innerText).toBe('00:00:42');
+ beforeEach(() => {
+ jest
+ .spyOn(Date, 'now')
+ .mockImplementation(
+ () => mockGraphQlJob.scheduledAt.getTime() - remainingTimeInMilliseconds,
+ );
+
+ vm = mountComponent(dummyComponent, {
+ job: mockGraphQlJob,
+ });
});
- it('updates remaining time', () => {
- remainingTimeInMilliseconds = 41000;
- jest.advanceTimersByTime(1000);
+ describe('after mounting', () => {
+ beforeEach(() => vm.$nextTick());
+
+ it('sets remaining time', () => {
+ expect(vm.$el.innerText).toBe('00:00:42');
+ });
+
+ it('updates remaining time', () => {
+ remainingTimeInMilliseconds = 41000;
+ jest.advanceTimersByTime(1000);
- return vm.$nextTick().then(() => {
- expect(vm.$el.innerText).toBe('00:00:41');
+ return vm.$nextTick().then(() => {
+ expect(vm.$el.innerText).toBe('00:00:41');
+ });
});
});
});
diff --git a/spec/frontend/jobs/store/actions_spec.js b/spec/frontend/jobs/store/actions_spec.js
index 91bd5521f70..26547d12ac7 100644
--- a/spec/frontend/jobs/store/actions_spec.js
+++ b/spec/frontend/jobs/store/actions_spec.js
@@ -27,6 +27,7 @@ import {
hideSidebar,
showSidebar,
toggleSidebar,
+ triggerManualJob,
} from '~/jobs/store/actions';
import state from '~/jobs/store/state';
import * as types from '~/jobs/store/mutation_types';
@@ -158,6 +159,32 @@ describe('Job State actions', () => {
);
});
});
+
+ it('fetchTrace is called only if the job has started or has a trace', done => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, { id: 121212, name: 'karma' });
+
+ mockedState.job.started = true;
+
+ testAction(
+ fetchJob,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestJob',
+ },
+ {
+ payload: { id: 121212, name: 'karma' },
+ type: 'receiveJobSuccess',
+ },
+ {
+ type: 'fetchTrace',
+ },
+ ],
+ done,
+ );
+ });
});
describe('receiveJobSuccess', () => {
@@ -509,4 +536,43 @@ describe('Job State actions', () => {
);
});
});
+
+ describe('triggerManualJob', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should dispatch fetchTrace', done => {
+ const playManualJobEndpoint = `${TEST_HOST}/manual-job/jobs/1000/play`;
+
+ mock.onPost(playManualJobEndpoint).reply(200);
+
+ mockedState.job = {
+ status: {
+ action: {
+ path: playManualJobEndpoint,
+ },
+ },
+ };
+
+ testAction(
+ triggerManualJob,
+ [{ id: '1', key: 'test_var', secret_value: 'test_value' }],
+ mockedState,
+ [],
+ [
+ {
+ type: 'fetchTrace',
+ },
+ ],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js
index 608abc8f7c4..a8146ba93eb 100644
--- a/spec/frontend/jobs/store/mutations_spec.js
+++ b/spec/frontend/jobs/store/mutations_spec.js
@@ -153,6 +153,7 @@ describe('Jobs Store Mutations', () => {
mutations[types.SET_TRACE_TIMEOUT](stateCopy, id);
expect(stateCopy.traceTimeout).toEqual(id);
+ expect(stateCopy.isTraceComplete).toBe(false);
});
});
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index 09eb362c77e..433fb368f55 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -220,6 +220,7 @@ describe('common_utils', () => {
beforeEach(() => {
elem = document.createElement('div');
window.innerHeight = windowHeight;
+ window.mrTabs = { currentAction: 'show' };
jest.spyOn($.fn, 'animate');
jest.spyOn($.fn, 'offset').mockReturnValue({ top: elemTop });
});
@@ -525,8 +526,8 @@ describe('common_utils', () => {
});
it('should set svg className when passed', () => {
- expect(commonUtils.spriteIcon('test', 'fa fa-test')).toEqual(
- '<svg class="fa fa-test"><use xlink:href="icons.svg#test" /></svg>',
+ expect(commonUtils.spriteIcon('test', 'first-icon-class second-icon-class')).toEqual(
+ '<svg class="first-icon-class second-icon-class"><use xlink:href="icons.svg#test" /></svg>',
);
});
});
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index d7cedb939d2..9c50bf577dc 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -340,4 +340,27 @@ describe('text_utility', () => {
expect(textUtils.isValidSha1Hash(hash)).toBe(valid);
});
});
+
+ describe('insertFinalNewline', () => {
+ it.each`
+ input | output
+ ${'some text'} | ${'some text\n'}
+ ${'some text\n'} | ${'some text\n'}
+ ${'some text\n\n'} | ${'some text\n\n'}
+ ${'some\n text'} | ${'some\n text\n'}
+ `('adds a newline if it doesnt already exist for input: $input', ({ input, output }) => {
+ expect(textUtils.insertFinalNewline(input)).toBe(output);
+ });
+
+ it.each`
+ input | output
+ ${'some text'} | ${'some text\r\n'}
+ ${'some text\r\n'} | ${'some text\r\n'}
+ ${'some text\n'} | ${'some text\n\r\n'}
+ ${'some text\r\n\r\n'} | ${'some text\r\n\r\n'}
+ ${'some\r\n text'} | ${'some\r\n text\r\n'}
+ `('works with CRLF newline style; input: $input', ({ input, output }) => {
+ expect(textUtils.insertFinalNewline(input, '\r\n')).toBe(output);
+ });
+ });
});
diff --git a/spec/frontend/maintenance_mode_settings/components/app_spec.js b/spec/frontend/maintenance_mode_settings/components/app_spec.js
deleted file mode 100644
index ad753642e85..00000000000
--- a/spec/frontend/maintenance_mode_settings/components/app_spec.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlToggle, GlFormTextarea, GlButton } from '@gitlab/ui';
-import MaintenanceModeSettingsApp from '~/maintenance_mode_settings/components/app.vue';
-
-describe('MaintenanceModeSettingsApp', () => {
- let wrapper;
-
- const createComponent = () => {
- wrapper = shallowMount(MaintenanceModeSettingsApp);
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findMaintenanceModeSettingsContainer = () => wrapper.find('article');
- const findGlToggle = () => wrapper.find(GlToggle);
- const findGlFormTextarea = () => wrapper.find(GlFormTextarea);
- const findGlButton = () => wrapper.find(GlButton);
-
- describe('template', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders the Maintenance Mode Settings container', () => {
- expect(findMaintenanceModeSettingsContainer().exists()).toBe(true);
- });
-
- it('renders the GlToggle', () => {
- expect(findGlToggle().exists()).toBe(true);
- });
-
- it('renders the GlFormTextarea', () => {
- expect(findGlFormTextarea().exists()).toBe(true);
- });
-
- it('renders the GlButton', () => {
- expect(findGlButton().exists()).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/members/action_buttons/access_request_action_buttons_spec.js b/spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js
index 58cb8ef61d1..9a8434a1222 100644
--- a/spec/frontend/vue_shared/components/members/action_buttons/access_request_action_buttons_spec.js
+++ b/spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js
@@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils';
-import AccessRequestActionButtons from '~/vue_shared/components/members/action_buttons/access_request_action_buttons.vue';
-import RemoveMemberButton from '~/vue_shared/components/members/action_buttons/remove_member_button.vue';
-import ApproveAccessRequestButton from '~/vue_shared/components/members/action_buttons/approve_access_request_button.vue';
-import { accessRequest as member } from '../mock_data';
+import AccessRequestActionButtons from '~/members/components/action_buttons/access_request_action_buttons.vue';
+import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
+import ApproveAccessRequestButton from '~/members/components/action_buttons/approve_access_request_button.vue';
+import { accessRequest as member } from '../../mock_data';
describe('AccessRequestActionButtons', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/members/action_buttons/approve_access_request_button_spec.js b/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js
index 93edaaa400d..7ce2c633bb3 100644
--- a/spec/frontend/vue_shared/components/members/action_buttons/approve_access_request_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js
@@ -2,7 +2,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlButton, GlForm } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import ApproveAccessRequestButton from '~/vue_shared/components/members/action_buttons/approve_access_request_button.vue';
+import ApproveAccessRequestButton from '~/members/components/action_buttons/approve_access_request_button.vue';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
diff --git a/spec/frontend/vue_shared/components/members/action_buttons/invite_action_buttons_spec.js b/spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js
index 1374cdc6aef..887b21dc1d0 100644
--- a/spec/frontend/vue_shared/components/members/action_buttons/invite_action_buttons_spec.js
+++ b/spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js
@@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils';
-import InviteActionButtons from '~/vue_shared/components/members/action_buttons/invite_action_buttons.vue';
-import RemoveMemberButton from '~/vue_shared/components/members/action_buttons/remove_member_button.vue';
-import ResendInviteButton from '~/vue_shared/components/members/action_buttons/resend_invite_button.vue';
-import { invite as member } from '../mock_data';
+import InviteActionButtons from '~/members/components/action_buttons/invite_action_buttons.vue';
+import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
+import ResendInviteButton from '~/members/components/action_buttons/resend_invite_button.vue';
+import { invite as member } from '../../mock_data';
describe('InviteActionButtons', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/members/action_buttons/leave_button_spec.js b/spec/frontend/members/components/action_buttons/leave_button_spec.js
index 00896b23b95..2afe112c74b 100644
--- a/spec/frontend/vue_shared/components/members/action_buttons/leave_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/leave_button_spec.js
@@ -1,10 +1,10 @@
import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import LeaveButton from '~/vue_shared/components/members/action_buttons/leave_button.vue';
-import LeaveModal from '~/vue_shared/components/members/modals/leave_modal.vue';
-import { LEAVE_MODAL_ID } from '~/vue_shared/components/members/constants';
-import { member } from '../mock_data';
+import LeaveButton from '~/members/components/action_buttons/leave_button.vue';
+import LeaveModal from '~/members/components/modals/leave_modal.vue';
+import { LEAVE_MODAL_ID } from '~/members/constants';
+import { member } from '../../mock_data';
describe('LeaveButton', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/members/action_buttons/remove_group_link_button_spec.js b/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js
index 84fe1c51773..45283788676 100644
--- a/spec/frontend/vue_shared/components/members/action_buttons/remove_group_link_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js
@@ -2,8 +2,8 @@ import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlButton } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import RemoveGroupLinkButton from '~/vue_shared/components/members/action_buttons/remove_group_link_button.vue';
-import { group } from '../mock_data';
+import RemoveGroupLinkButton from '~/members/components/action_buttons/remove_group_link_button.vue';
+import { group } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/vue_shared/components/members/action_buttons/remove_member_button_spec.js b/spec/frontend/members/components/action_buttons/remove_member_button_spec.js
index 7aa30494234..437b3e705a4 100644
--- a/spec/frontend/vue_shared/components/members/action_buttons/remove_member_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/remove_member_button_spec.js
@@ -1,7 +1,7 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import RemoveMemberButton from '~/vue_shared/components/members/action_buttons/remove_member_button.vue';
+import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/vue_shared/components/members/action_buttons/resend_invite_button_spec.js b/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js
index 859fdd01043..a48942dd277 100644
--- a/spec/frontend/vue_shared/components/members/action_buttons/resend_invite_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js
@@ -2,7 +2,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlButton } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import ResendInviteButton from '~/vue_shared/components/members/action_buttons/resend_invite_button.vue';
+import ResendInviteButton from '~/members/components/action_buttons/resend_invite_button.vue';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
diff --git a/spec/frontend/vue_shared/components/members/action_buttons/user_action_buttons_spec.js b/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js
index f766ad5b0d1..b03e80a537d 100644
--- a/spec/frontend/vue_shared/components/members/action_buttons/user_action_buttons_spec.js
+++ b/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js
@@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils';
-import UserActionButtons from '~/vue_shared/components/members/action_buttons/user_action_buttons.vue';
-import RemoveMemberButton from '~/vue_shared/components/members/action_buttons/remove_member_button.vue';
-import LeaveButton from '~/vue_shared/components/members/action_buttons/leave_button.vue';
-import { member, orphanedMember } from '../mock_data';
+import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue';
+import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
+import LeaveButton from '~/members/components/action_buttons/leave_button.vue';
+import { member, orphanedMember } from '../../mock_data';
describe('UserActionButtons', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/members/avatars/group_avatar_spec.js b/spec/frontend/members/components/avatars/group_avatar_spec.js
index d6f5773295c..658bb9462b0 100644
--- a/spec/frontend/vue_shared/components/members/avatars/group_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/group_avatar_spec.js
@@ -1,8 +1,8 @@
import { mount, createWrapper } from '@vue/test-utils';
import { getByText as getByTextHelper } from '@testing-library/dom';
import { GlAvatarLink } from '@gitlab/ui';
-import { group as member } from '../mock_data';
-import GroupAvatar from '~/vue_shared/components/members/avatars/group_avatar.vue';
+import { group as member } from '../../mock_data';
+import GroupAvatar from '~/members/components/avatars/group_avatar.vue';
describe('MemberList', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/members/avatars/invite_avatar_spec.js b/spec/frontend/members/components/avatars/invite_avatar_spec.js
index 7948da7eb40..13ee727528b 100644
--- a/spec/frontend/vue_shared/components/members/avatars/invite_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/invite_avatar_spec.js
@@ -1,7 +1,7 @@
import { mount, createWrapper } from '@vue/test-utils';
import { getByText as getByTextHelper } from '@testing-library/dom';
-import { invite as member } from '../mock_data';
-import InviteAvatar from '~/vue_shared/components/members/avatars/invite_avatar.vue';
+import { invite as member } from '../../mock_data';
+import InviteAvatar from '~/members/components/avatars/invite_avatar.vue';
describe('MemberList', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/members/avatars/user_avatar_spec.js b/spec/frontend/members/components/avatars/user_avatar_spec.js
index 93d8e640968..7d6a9065975 100644
--- a/spec/frontend/vue_shared/components/members/avatars/user_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/user_avatar_spec.js
@@ -1,8 +1,8 @@
import { mount, createWrapper } from '@vue/test-utils';
import { within } from '@testing-library/dom';
import { GlAvatarLink, GlBadge } from '@gitlab/ui';
-import { member as memberMock, orphanedMember } from '../mock_data';
-import UserAvatar from '~/vue_shared/components/members/avatars/user_avatar.vue';
+import { member as memberMock, orphanedMember } from '../../mock_data';
+import UserAvatar from '~/members/components/avatars/user_avatar.vue';
describe('UserAvatar', () => {
let wrapper;
diff --git a/spec/frontend/members/components/filter_sort/filter_sort_container_spec.js b/spec/frontend/members/components/filter_sort/filter_sort_container_spec.js
new file mode 100644
index 00000000000..91277ae6d03
--- /dev/null
+++ b/spec/frontend/members/components/filter_sort/filter_sort_container_spec.js
@@ -0,0 +1,68 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
+import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
+import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('FilterSortContainer', () => {
+ let wrapper;
+
+ const createComponent = state => {
+ const store = new Vuex.Store({
+ state: {
+ filteredSearchBar: {
+ show: true,
+ tokens: ['two_factor'],
+ searchParam: 'search',
+ placeholder: 'Filter members',
+ recentSearchesStorageKey: 'group_members',
+ },
+ tableSortableFields: ['account'],
+ ...state,
+ },
+ });
+
+ wrapper = shallowMount(FilterSortContainer, {
+ localVue,
+ store,
+ });
+ };
+
+ describe('when `filteredSearchBar.show` is `false` and `tableSortableFields` is empty', () => {
+ it('renders nothing', () => {
+ createComponent({
+ filteredSearchBar: {
+ show: false,
+ },
+ tableSortableFields: [],
+ });
+
+ expect(wrapper.html()).toBe('');
+ });
+ });
+
+ describe('when `filteredSearchBar.show` is `true`', () => {
+ it('renders `MembersFilteredSearchBar`', () => {
+ createComponent({
+ filteredSearchBar: {
+ show: true,
+ },
+ });
+
+ expect(wrapper.find(MembersFilteredSearchBar).exists()).toBe(true);
+ });
+ });
+
+ describe('when `tableSortableFields` is set', () => {
+ it('renders `SortDropdown`', () => {
+ createComponent({
+ tableSortableFields: ['account'],
+ });
+
+ expect(wrapper.find(SortDropdown).exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
new file mode 100644
index 00000000000..ca885000c2f
--- /dev/null
+++ b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
@@ -0,0 +1,176 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { GlFilteredSearchToken } from '@gitlab/ui';
+import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
+import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('MembersFilteredSearchBar', () => {
+ let wrapper;
+
+ const createComponent = state => {
+ const store = new Vuex.Store({
+ state: {
+ sourceId: 1,
+ filteredSearchBar: {
+ show: true,
+ tokens: ['two_factor'],
+ searchParam: 'search',
+ placeholder: 'Filter members',
+ recentSearchesStorageKey: 'group_members',
+ },
+ canManageMembers: true,
+ ...state,
+ },
+ });
+
+ wrapper = shallowMount(MembersFilteredSearchBar, {
+ localVue,
+ store,
+ });
+ };
+
+ const findFilteredSearchBar = () => wrapper.find(FilteredSearchBar);
+
+ it('passes correct props to `FilteredSearchBar` component', () => {
+ createComponent();
+
+ expect(findFilteredSearchBar().props()).toMatchObject({
+ namespace: '1',
+ recentSearchesStorageKey: 'group_members',
+ searchInputPlaceholder: 'Filter members',
+ });
+ });
+
+ describe('filtering tokens', () => {
+ it('includes tokens set in `filteredSearchBar.tokens`', () => {
+ createComponent();
+
+ expect(findFilteredSearchBar().props('tokens')).toEqual([
+ {
+ type: 'two_factor',
+ icon: 'lock',
+ title: '2FA',
+ token: GlFilteredSearchToken,
+ unique: true,
+ operators: [{ value: '=', description: 'is' }],
+ options: [
+ { value: 'enabled', title: 'Enabled' },
+ { value: 'disabled', title: 'Disabled' },
+ ],
+ requiredPermissions: 'canManageMembers',
+ },
+ ]);
+ });
+
+ describe('when `canManageMembers` is false', () => {
+ it('excludes 2FA token', () => {
+ createComponent({
+ filteredSearchBar: {
+ show: true,
+ tokens: ['two_factor', 'with_inherited_permissions'],
+ searchParam: 'search',
+ placeholder: 'Filter members',
+ recentSearchesStorageKey: 'group_members',
+ },
+ canManageMembers: false,
+ });
+
+ expect(findFilteredSearchBar().props('tokens')).toEqual([
+ {
+ type: 'with_inherited_permissions',
+ icon: 'group',
+ title: 'Membership',
+ token: GlFilteredSearchToken,
+ unique: true,
+ operators: [{ value: '=', description: 'is' }],
+ options: [{ value: 'exclude', title: 'Direct' }, { value: 'only', title: 'Inherited' }],
+ },
+ ]);
+ });
+ });
+ });
+
+ describe('when filters are set via query params', () => {
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL('https://localhost');
+ });
+
+ it('parses and passes tokens to `FilteredSearchBar` component as `initialFilterValue` prop', () => {
+ window.location.search = '?two_factor=enabled&token_not_available=foobar';
+
+ createComponent();
+
+ expect(findFilteredSearchBar().props('initialFilterValue')).toEqual([
+ {
+ type: 'two_factor',
+ value: {
+ data: 'enabled',
+ operator: '=',
+ },
+ },
+ ]);
+ });
+
+ it('parses and passes search param to `FilteredSearchBar` component as `initialFilterValue` prop', () => {
+ window.location.search = '?search=foobar';
+
+ createComponent();
+
+ expect(findFilteredSearchBar().props('initialFilterValue')).toEqual([
+ {
+ type: 'filtered-search-term',
+ value: {
+ data: 'foobar',
+ },
+ },
+ ]);
+ });
+ });
+
+ describe('when filter bar is submitted', () => {
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL('https://localhost');
+ });
+
+ it('adds correct filter query params', () => {
+ createComponent();
+
+ findFilteredSearchBar().vm.$emit('onFilter', [
+ { type: 'two_factor', value: { data: 'enabled', operator: '=' } },
+ ]);
+
+ expect(window.location.href).toBe('https://localhost/?two_factor=enabled');
+ });
+
+ it('adds search query param', () => {
+ createComponent();
+
+ findFilteredSearchBar().vm.$emit('onFilter', [
+ { type: 'two_factor', value: { data: 'enabled', operator: '=' } },
+ { type: 'filtered-search-term', value: { data: 'foobar' } },
+ ]);
+
+ expect(window.location.href).toBe('https://localhost/?two_factor=enabled&search=foobar');
+ });
+
+ it('adds sort query param', () => {
+ window.location.search = '?sort=name_asc';
+
+ createComponent();
+
+ findFilteredSearchBar().vm.$emit('onFilter', [
+ { type: 'two_factor', value: { data: 'enabled', operator: '=' } },
+ { type: 'filtered-search-term', value: { data: 'foobar' } },
+ ]);
+
+ expect(window.location.href).toBe(
+ 'https://localhost/?two_factor=enabled&search=foobar&sort=name_asc',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js
new file mode 100644
index 00000000000..6fe67aded3d
--- /dev/null
+++ b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js
@@ -0,0 +1,162 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { GlSorting, GlSortingItem } from '@gitlab/ui';
+import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue';
+import * as urlUtilities from '~/lib/utils/url_utility';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('SortDropdown', () => {
+ let wrapper;
+
+ const URL_HOST = 'https://localhost/';
+
+ const createComponent = state => {
+ const store = new Vuex.Store({
+ state: {
+ sourceId: 1,
+ tableSortableFields: ['account', 'granted', 'expires', 'maxRole', 'lastSignIn'],
+ filteredSearchBar: {
+ show: true,
+ tokens: ['two_factor'],
+ searchParam: 'search',
+ placeholder: 'Filter members',
+ recentSearchesStorageKey: 'group_members',
+ },
+ ...state,
+ },
+ });
+
+ wrapper = mount(SortDropdown, {
+ localVue,
+ store,
+ });
+ };
+
+ const findSortingComponent = () => wrapper.find(GlSorting);
+ const findSortDirectionToggle = () =>
+ findSortingComponent().find('button[title="Sort direction"]');
+ const findDropdownToggle = () => wrapper.find('button[aria-haspopup="true"]');
+ const findDropdownItemByText = text =>
+ wrapper
+ .findAll(GlSortingItem)
+ .wrappers.find(dropdownItemWrapper => dropdownItemWrapper.text() === text);
+
+ describe('dropdown options', () => {
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL(URL_HOST);
+ });
+
+ it('adds dropdown items for all the sortable fields', () => {
+ const URL_FILTER_PARAMS = '?two_factor=enabled&search=foobar';
+ const EXPECTED_BASE_URL = `${URL_HOST}${URL_FILTER_PARAMS}&sort=`;
+
+ window.location.search = URL_FILTER_PARAMS;
+
+ const expectedDropdownItems = [
+ {
+ label: 'Account',
+ url: `${EXPECTED_BASE_URL}name_asc`,
+ },
+ {
+ label: 'Access granted',
+ url: `${EXPECTED_BASE_URL}last_joined`,
+ },
+ {
+ label: 'Max role',
+ url: `${EXPECTED_BASE_URL}access_level_asc`,
+ },
+ {
+ label: 'Last sign-in',
+ url: `${EXPECTED_BASE_URL}recent_sign_in`,
+ },
+ ];
+
+ createComponent();
+
+ expectedDropdownItems.forEach(expectedDropdownItem => {
+ const dropdownItem = findDropdownItemByText(expectedDropdownItem.label);
+
+ expect(dropdownItem).not.toBe(null);
+ expect(dropdownItem.find('a').attributes('href')).toBe(expectedDropdownItem.url);
+ });
+ });
+
+ it('checks selected sort option', () => {
+ window.location.search = '?sort=access_level_asc';
+
+ createComponent();
+
+ expect(findDropdownItemByText('Max role').vm.$attrs.active).toBe(true);
+ });
+ });
+
+ describe('dropdown toggle', () => {
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL(URL_HOST);
+ });
+
+ it('defaults to sorting by "Account" in ascending order', () => {
+ createComponent();
+
+ expect(findSortingComponent().props('isAscending')).toBe(true);
+ expect(findDropdownToggle().text()).toBe('Account');
+ });
+
+ it('sets text as selected sort option', () => {
+ window.location.search = '?sort=access_level_asc';
+
+ createComponent();
+
+ expect(findDropdownToggle().text()).toBe('Max role');
+ });
+ });
+
+ describe('sort direction toggle', () => {
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL(URL_HOST);
+
+ jest.spyOn(urlUtilities, 'visitUrl');
+ });
+
+ describe('when current sort direction is ascending', () => {
+ beforeEach(() => {
+ window.location.search = '?sort=access_level_asc';
+
+ createComponent();
+ });
+
+ describe('when sort direction toggle is clicked', () => {
+ beforeEach(() => {
+ findSortDirectionToggle().trigger('click');
+ });
+
+ it('sorts in descending order', () => {
+ expect(urlUtilities.visitUrl).toHaveBeenCalledWith(`${URL_HOST}?sort=access_level_desc`);
+ });
+ });
+ });
+
+ describe('when current sort direction is descending', () => {
+ beforeEach(() => {
+ window.location.search = '?sort=access_level_desc';
+
+ createComponent();
+ });
+
+ describe('when sort direction toggle is clicked', () => {
+ beforeEach(() => {
+ findSortDirectionToggle().trigger('click');
+ });
+
+ it('sorts in ascending order', () => {
+ expect(urlUtilities.visitUrl).toHaveBeenCalledWith(`${URL_HOST}?sort=access_level_asc`);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/members/modals/leave_modal_spec.js b/spec/frontend/members/components/modals/leave_modal_spec.js
index 63de355a3c8..d7acf12212c 100644
--- a/spec/frontend/vue_shared/components/members/modals/leave_modal_spec.js
+++ b/spec/frontend/members/components/modals/leave_modal_spec.js
@@ -3,9 +3,9 @@ import { GlModal, GlForm } from '@gitlab/ui';
import { nextTick } from 'vue';
import { within } from '@testing-library/dom';
import Vuex from 'vuex';
-import LeaveModal from '~/vue_shared/components/members/modals/leave_modal.vue';
-import { LEAVE_MODAL_ID } from '~/vue_shared/components/members/constants';
-import { member } from '../mock_data';
+import LeaveModal from '~/members/components/modals/leave_modal.vue';
+import { LEAVE_MODAL_ID } from '~/members/constants';
+import { member } from '../../mock_data';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
diff --git a/spec/frontend/vue_shared/components/members/modals/remove_group_link_modal_spec.js b/spec/frontend/members/components/modals/remove_group_link_modal_spec.js
index 84da051792d..593dbcd28ba 100644
--- a/spec/frontend/vue_shared/components/members/modals/remove_group_link_modal_spec.js
+++ b/spec/frontend/members/components/modals/remove_group_link_modal_spec.js
@@ -3,9 +3,9 @@ import { GlModal, GlForm } from '@gitlab/ui';
import { nextTick } from 'vue';
import { within } from '@testing-library/dom';
import Vuex from 'vuex';
-import RemoveGroupLinkModal from '~/vue_shared/components/members/modals/remove_group_link_modal.vue';
-import { REMOVE_GROUP_LINK_MODAL_ID } from '~/vue_shared/components/members/constants';
-import { group } from '../mock_data';
+import RemoveGroupLinkModal from '~/members/components/modals/remove_group_link_modal.vue';
+import { REMOVE_GROUP_LINK_MODAL_ID } from '~/members/constants';
+import { group } from '../../mock_data';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
diff --git a/spec/frontend/vue_shared/components/members/table/created_at_spec.js b/spec/frontend/members/components/table/created_at_spec.js
index cf3821baf44..a9f809cd805 100644
--- a/spec/frontend/vue_shared/components/members/table/created_at_spec.js
+++ b/spec/frontend/members/components/table/created_at_spec.js
@@ -1,7 +1,7 @@
import { mount, createWrapper } from '@vue/test-utils';
import { within } from '@testing-library/dom';
import { useFakeDate } from 'helpers/fake_date';
-import CreatedAt from '~/vue_shared/components/members/table/created_at.vue';
+import CreatedAt from '~/members/components/table/created_at.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
describe('CreatedAt', () => {
diff --git a/spec/frontend/vue_shared/components/members/table/expiration_datepicker_spec.js b/spec/frontend/members/components/table/expiration_datepicker_spec.js
index a1afdbc2b49..ba1b2256e76 100644
--- a/spec/frontend/vue_shared/components/members/table/expiration_datepicker_spec.js
+++ b/spec/frontend/members/components/table/expiration_datepicker_spec.js
@@ -4,8 +4,8 @@ import { nextTick } from 'vue';
import { GlDatepicker } from '@gitlab/ui';
import { useFakeDate } from 'helpers/fake_date';
import waitForPromises from 'helpers/wait_for_promises';
-import ExpirationDatepicker from '~/vue_shared/components/members/table/expiration_datepicker.vue';
-import { member } from '../mock_data';
+import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue';
+import { member } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/vue_shared/components/members/table/expires_at_spec.js b/spec/frontend/members/components/table/expires_at_spec.js
index 95ae251b0fd..cf0fc78656e 100644
--- a/spec/frontend/vue_shared/components/members/table/expires_at_spec.js
+++ b/spec/frontend/members/components/table/expires_at_spec.js
@@ -2,7 +2,7 @@ import { mount, createWrapper } from '@vue/test-utils';
import { within } from '@testing-library/dom';
import { useFakeDate } from 'helpers/fake_date';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import ExpiresAt from '~/vue_shared/components/members/table/expires_at.vue';
+import ExpiresAt from '~/members/components/table/expires_at.vue';
describe('ExpiresAt', () => {
// March 15th, 2020
diff --git a/spec/frontend/vue_shared/components/members/table/member_action_buttons_spec.js b/spec/frontend/members/components/table/member_action_buttons_spec.js
index e55d9b6be2a..b7a6df3d054 100644
--- a/spec/frontend/vue_shared/components/members/table/member_action_buttons_spec.js
+++ b/spec/frontend/members/components/table/member_action_buttons_spec.js
@@ -1,11 +1,11 @@
import { shallowMount } from '@vue/test-utils';
-import { MEMBER_TYPES } from '~/vue_shared/components/members/constants';
-import { member as memberMock, group, invite, accessRequest } from '../mock_data';
-import MemberActionButtons from '~/vue_shared/components/members/table/member_action_buttons.vue';
-import UserActionButtons from '~/vue_shared/components/members/action_buttons/user_action_buttons.vue';
-import GroupActionButtons from '~/vue_shared/components/members/action_buttons/group_action_buttons.vue';
-import InviteActionButtons from '~/vue_shared/components/members/action_buttons/invite_action_buttons.vue';
-import AccessRequestActionButtons from '~/vue_shared/components/members/action_buttons/access_request_action_buttons.vue';
+import { MEMBER_TYPES } from '~/members/constants';
+import { member as memberMock, group, invite, accessRequest } from '../../mock_data';
+import MemberActionButtons from '~/members/components/table/member_action_buttons.vue';
+import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue';
+import GroupActionButtons from '~/members/components/action_buttons/group_action_buttons.vue';
+import InviteActionButtons from '~/members/components/action_buttons/invite_action_buttons.vue';
+import AccessRequestActionButtons from '~/members/components/action_buttons/access_request_action_buttons.vue';
describe('MemberActionButtons', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/members/table/member_avatar_spec.js b/spec/frontend/members/components/table/member_avatar_spec.js
index a171dd830c1..98177893c18 100644
--- a/spec/frontend/vue_shared/components/members/table/member_avatar_spec.js
+++ b/spec/frontend/members/components/table/member_avatar_spec.js
@@ -1,10 +1,10 @@
import { shallowMount } from '@vue/test-utils';
-import { MEMBER_TYPES } from '~/vue_shared/components/members/constants';
-import { member as memberMock, group, invite, accessRequest } from '../mock_data';
-import MemberAvatar from '~/vue_shared/components/members/table/member_avatar.vue';
-import UserAvatar from '~/vue_shared/components/members/avatars/user_avatar.vue';
-import GroupAvatar from '~/vue_shared/components/members/avatars/group_avatar.vue';
-import InviteAvatar from '~/vue_shared/components/members/avatars/invite_avatar.vue';
+import { MEMBER_TYPES } from '~/members/constants';
+import { member as memberMock, group, invite, accessRequest } from '../../mock_data';
+import MemberAvatar from '~/members/components/table/member_avatar.vue';
+import UserAvatar from '~/members/components/avatars/user_avatar.vue';
+import GroupAvatar from '~/members/components/avatars/group_avatar.vue';
+import InviteAvatar from '~/members/components/avatars/invite_avatar.vue';
describe('MemberList', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/members/table/member_source_spec.js b/spec/frontend/members/components/table/member_source_spec.js
index 8b914d76674..48ac06f32f6 100644
--- a/spec/frontend/vue_shared/components/members/table/member_source_spec.js
+++ b/spec/frontend/members/components/table/member_source_spec.js
@@ -1,7 +1,7 @@
import { mount, createWrapper } from '@vue/test-utils';
import { getByText as getByTextHelper } from '@testing-library/dom';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import MemberSource from '~/vue_shared/components/members/table/member_source.vue';
+import MemberSource from '~/members/components/table/member_source.vue';
describe('MemberSource', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/members/table/member_table_cell_spec.js b/spec/frontend/members/components/table/members_table_cell_spec.js
index ba693975a88..117c9255c00 100644
--- a/spec/frontend/vue_shared/components/members/table/member_table_cell_spec.js
+++ b/spec/frontend/members/components/table/members_table_cell_spec.js
@@ -1,10 +1,10 @@
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { MEMBER_TYPES } from '~/vue_shared/components/members/constants';
-import { member as memberMock, group, invite, accessRequest } from '../mock_data';
-import MembersTableCell from '~/vue_shared/components/members/table/members_table_cell.vue';
+import { MEMBER_TYPES } from '~/members/constants';
+import { member as memberMock, group, invite, accessRequest } from '../../mock_data';
+import MembersTableCell from '~/members/components/table/members_table_cell.vue';
-describe('MemberList', () => {
+describe('MembersTableCell', () => {
const WrappedComponent = {
props: {
memberType: {
diff --git a/spec/frontend/vue_shared/components/members/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js
index e593e88438c..9945cc7ee57 100644
--- a/spec/frontend/vue_shared/components/members/table/members_table_spec.js
+++ b/spec/frontend/members/components/table/members_table_spec.js
@@ -6,21 +6,21 @@ import {
within,
} from '@testing-library/dom';
import { GlBadge, GlTable } from '@gitlab/ui';
-import MembersTable from '~/vue_shared/components/members/table/members_table.vue';
-import MemberAvatar from '~/vue_shared/components/members/table/member_avatar.vue';
-import MemberSource from '~/vue_shared/components/members/table/member_source.vue';
-import ExpiresAt from '~/vue_shared/components/members/table/expires_at.vue';
-import CreatedAt from '~/vue_shared/components/members/table/created_at.vue';
-import RoleDropdown from '~/vue_shared/components/members/table/role_dropdown.vue';
-import ExpirationDatepicker from '~/vue_shared/components/members/table/expiration_datepicker.vue';
-import MemberActionButtons from '~/vue_shared/components/members/table/member_action_buttons.vue';
+import MembersTable from '~/members/components/table/members_table.vue';
+import MemberAvatar from '~/members/components/table/member_avatar.vue';
+import MemberSource from '~/members/components/table/member_source.vue';
+import ExpiresAt from '~/members/components/table/expires_at.vue';
+import CreatedAt from '~/members/components/table/created_at.vue';
+import RoleDropdown from '~/members/components/table/role_dropdown.vue';
+import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue';
+import MemberActionButtons from '~/members/components/table/member_action_buttons.vue';
import * as initUserPopovers from '~/user_popovers';
-import { member as memberMock, invite, accessRequest } from '../mock_data';
+import { member as memberMock, invite, accessRequest } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
-describe('MemberList', () => {
+describe('MembersTable', () => {
let wrapper;
const createStore = (state = {}) => {
diff --git a/spec/frontend/vue_shared/components/members/table/role_dropdown_spec.js b/spec/frontend/members/components/table/role_dropdown_spec.js
index 55ec7000693..6c6abf35bd7 100644
--- a/spec/frontend/vue_shared/components/members/table/role_dropdown_spec.js
+++ b/spec/frontend/members/components/table/role_dropdown_spec.js
@@ -5,8 +5,8 @@ import { within } from '@testing-library/dom';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import waitForPromises from 'helpers/wait_for_promises';
-import RoleDropdown from '~/vue_shared/components/members/table/role_dropdown.vue';
-import { member } from '../mock_data';
+import RoleDropdown from '~/members/components/table/role_dropdown.vue';
+import { member } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -137,7 +137,7 @@ describe('RoleDropdown', () => {
await nextTick();
- expect(findDropdown().attributes('right')).toBe('true');
+ expect(findDropdown().props('right')).toBe(true);
});
it('sets the dropdown alignment to left on desktop', async () => {
@@ -146,6 +146,6 @@ describe('RoleDropdown', () => {
await nextTick();
- expect(findDropdown().attributes('right')).toBeUndefined();
+ expect(findDropdown().props('right')).toBe(false);
});
});
diff --git a/spec/frontend/vue_shared/components/members/mock_data.js b/spec/frontend/members/mock_data.js
index 5674929716d..5674929716d 100644
--- a/spec/frontend/vue_shared/components/members/mock_data.js
+++ b/spec/frontend/members/mock_data.js
diff --git a/spec/frontend/vuex_shared/modules/members/actions_spec.js b/spec/frontend/members/store/actions_spec.js
index c7048a9c421..5424fee0750 100644
--- a/spec/frontend/vuex_shared/modules/members/actions_spec.js
+++ b/spec/frontend/members/store/actions_spec.js
@@ -1,17 +1,17 @@
import { noop } from 'lodash';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import { members, group } from 'jest/vue_shared/components/members/mock_data';
+import { members, group } from 'jest/members/mock_data';
import testAction from 'helpers/vuex_action_helper';
import { useFakeDate } from 'helpers/fake_date';
import httpStatusCodes from '~/lib/utils/http_status';
-import * as types from '~/vuex_shared/modules/members/mutation_types';
+import * as types from '~/members/store/mutation_types';
import {
updateMemberRole,
showRemoveGroupLinkModal,
hideRemoveGroupLinkModal,
updateMemberExpiration,
-} from '~/vuex_shared/modules/members/actions';
+} from '~/members/store/actions';
describe('Vuex members actions', () => {
describe('update member actions', () => {
diff --git a/spec/frontend/vuex_shared/modules/members/mutations_spec.js b/spec/frontend/members/store/mutations_spec.js
index 710d43b8990..488bfdf15fd 100644
--- a/spec/frontend/vuex_shared/modules/members/mutations_spec.js
+++ b/spec/frontend/members/store/mutations_spec.js
@@ -1,6 +1,6 @@
-import { members, group } from 'jest/vue_shared/components/members/mock_data';
-import mutations from '~/vuex_shared/modules/members/mutations';
-import * as types from '~/vuex_shared/modules/members/mutation_types';
+import { members, group } from 'jest/members/mock_data';
+import mutations from '~/members/store/mutations';
+import * as types from '~/members/store/mutation_types';
describe('Vuex members mutations', () => {
describe('update member mutations', () => {
diff --git a/spec/frontend/vuex_shared/modules/members/utils_spec.js b/spec/frontend/members/store/utils_spec.js
index 4fc3445dac0..e3cde38269c 100644
--- a/spec/frontend/vuex_shared/modules/members/utils_spec.js
+++ b/spec/frontend/members/store/utils_spec.js
@@ -1,5 +1,5 @@
-import { members } from 'jest/vue_shared/components/members/mock_data';
-import { findMember } from '~/vuex_shared/modules/members/utils';
+import { members } from 'jest/members/mock_data';
+import { findMember } from '~/members/store/utils';
describe('Members Vuex utils', () => {
describe('findMember', () => {
diff --git a/spec/frontend/vue_shared/components/members/utils_spec.js b/spec/frontend/members/utils_spec.js
index 3f2b2097133..7bbfddf8fc6 100644
--- a/spec/frontend/vue_shared/components/members/utils_spec.js
+++ b/spec/frontend/members/utils_spec.js
@@ -7,13 +7,17 @@ import {
canResend,
canUpdate,
canOverride,
-} from '~/vue_shared/components/members/utils';
+ parseSortParam,
+ buildSortHref,
+} from '~/members/utils';
+import { DEFAULT_SORT } from '~/members/constants';
import { member as memberMock, group, invite } from './mock_data';
const DIRECT_MEMBER_ID = 178;
const INHERITED_MEMBER_ID = 179;
const IS_CURRENT_USER_ID = 123;
const IS_NOT_CURRENT_USER_ID = 124;
+const URL_HOST = 'https://localhost/';
describe('Members Utils', () => {
describe('generateBadges', () => {
@@ -119,4 +123,110 @@ describe('Members Utils', () => {
expect(canOverride(memberMock)).toBe(false);
});
});
+
+ describe('parseSortParam', () => {
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL(URL_HOST);
+ });
+
+ describe('when `sort` param is not present', () => {
+ it('returns default sort options', () => {
+ window.location.search = '';
+
+ expect(parseSortParam(['account'])).toEqual(DEFAULT_SORT);
+ });
+ });
+
+ describe('when field passed in `sortableFields` argument does not have `sort` key defined', () => {
+ it('returns default sort options', () => {
+ window.location.search = '?sort=source_asc';
+
+ expect(parseSortParam(['source'])).toEqual(DEFAULT_SORT);
+ });
+ });
+
+ describe.each`
+ sortParam | expected
+ ${'name_asc'} | ${{ sortByKey: 'account', sortDesc: false }}
+ ${'name_desc'} | ${{ sortByKey: 'account', sortDesc: true }}
+ ${'last_joined'} | ${{ sortByKey: 'granted', sortDesc: false }}
+ ${'oldest_joined'} | ${{ sortByKey: 'granted', sortDesc: true }}
+ ${'access_level_asc'} | ${{ sortByKey: 'maxRole', sortDesc: false }}
+ ${'access_level_desc'} | ${{ sortByKey: 'maxRole', sortDesc: true }}
+ ${'recent_sign_in'} | ${{ sortByKey: 'lastSignIn', sortDesc: false }}
+ ${'oldest_sign_in'} | ${{ sortByKey: 'lastSignIn', sortDesc: true }}
+ `('when `sort` query string param is `$sortParam`', ({ sortParam, expected }) => {
+ it(`returns ${JSON.stringify(expected)}`, async () => {
+ window.location.search = `?sort=${sortParam}`;
+
+ expect(parseSortParam(['account', 'granted', 'expires', 'maxRole', 'lastSignIn'])).toEqual(
+ expected,
+ );
+ });
+ });
+ });
+
+ describe('buildSortHref', () => {
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL(URL_HOST);
+ });
+
+ describe('when field passed in `sortBy` argument does not have `sort` key defined', () => {
+ it('returns an empty string', () => {
+ expect(
+ buildSortHref({
+ sortBy: 'source',
+ sortDesc: false,
+ filteredSearchBarTokens: [],
+ filteredSearchBarSearchParam: 'search',
+ }),
+ ).toBe('');
+ });
+ });
+
+ describe('when there are no filter params set', () => {
+ it('sets `sort` param', () => {
+ expect(
+ buildSortHref({
+ sortBy: 'account',
+ sortDesc: false,
+ filteredSearchBarTokens: [],
+ filteredSearchBarSearchParam: 'search',
+ }),
+ ).toBe(`${URL_HOST}?sort=name_asc`);
+ });
+ });
+
+ describe('when filter params are set', () => {
+ it('merges the `sort` param with the filter params', () => {
+ window.location.search = '?two_factor=enabled&with_inherited_permissions=exclude';
+
+ expect(
+ buildSortHref({
+ sortBy: 'account',
+ sortDesc: false,
+ filteredSearchBarTokens: ['two_factor', 'with_inherited_permissions'],
+ filteredSearchBarSearchParam: 'search',
+ }),
+ ).toBe(`${URL_HOST}?two_factor=enabled&with_inherited_permissions=exclude&sort=name_asc`);
+ });
+ });
+
+ describe('when search param is set', () => {
+ it('merges the `sort` param with the search param', () => {
+ window.location.search = '?search=foobar';
+
+ expect(
+ buildSortHref({
+ sortBy: 'account',
+ sortDesc: false,
+ filteredSearchBarTokens: ['two_factor', 'with_inherited_permissions'],
+ filteredSearchBarSearchParam: 'search',
+ }),
+ ).toBe(`${URL_HOST}?search=foobar&sort=name_asc`);
+ });
+ });
+ });
});
diff --git a/spec/frontend/merge_request_spec.js b/spec/frontend/merge_request_spec.js
index 37509f77f71..1cb7206b97f 100644
--- a/spec/frontend/merge_request_spec.js
+++ b/spec/frontend/merge_request_spec.js
@@ -38,7 +38,7 @@ describe('MergeRequest', () => {
.dispatchEvent(changeEvent);
setImmediate(() => {
expect($('.js-task-list-field').val()).toBe(
- '- [x] Task List Item\n- [ ] \n- [ ] Task List Item 2\n',
+ '- [x] Task List Item\n- [ ]\n- [ ] Task List Item 2\n',
);
done();
});
@@ -55,7 +55,7 @@ describe('MergeRequest', () => {
.dispatchEvent(changeEvent);
setImmediate(() => {
expect($('.js-task-list-field').val()).toBe(
- '- [ ] Task List Item\n- [ ] \n- [x] Task List Item 2\n',
+ '- [ ] Task List Item\n- [ ]\n- [x] Task List Item 2\n',
);
done();
});
@@ -78,7 +78,7 @@ describe('MergeRequest', () => {
`${TEST_HOST}/frontend-fixtures/merge-requests-project/-/merge_requests/1.json`,
{
merge_request: {
- description: '- [ ] Task List Item\n- [ ] \n- [ ] Task List Item 2\n',
+ description: '- [ ] Task List Item\n- [ ]\n- [ ] Task List Item 2\n',
lock_version: 0,
update_task: { line_number: lineNumber, line_source: lineSource, index, checked },
},
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
index 645aca0b157..17720aeb702 100644
--- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
@@ -33,7 +33,7 @@ exports[`Dashboard template matches the default snapshot 1`] = `
class="mb-2 pr-2 d-flex d-sm-block"
>
<gl-dropdown-stub
- category="tertiary"
+ category="primary"
class="flex-grow-1"
data-qa-selector="environments_dropdown"
headertext=""
diff --git a/spec/frontend/monitoring/components/group_empty_state_spec.js b/spec/frontend/monitoring/components/group_empty_state_spec.js
index 3b94c4c6806..4a550efe23c 100644
--- a/spec/frontend/monitoring/components/group_empty_state_spec.js
+++ b/spec/frontend/monitoring/components/group_empty_state_spec.js
@@ -1,13 +1,9 @@
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { stubComponent } from 'helpers/stub_component';
import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import { metricStates } from '~/monitoring/constants';
-const MockGlEmptyState = {
- props: GlEmptyState.props,
- template: '<div><slot name="description"></slot></div>',
-};
-
function createComponent(props) {
return shallowMount(GroupEmptyState, {
propsData: {
@@ -17,7 +13,9 @@ function createComponent(props) {
svgPath: '/path/to/empty-group-illustration.svg',
},
stubs: {
- GlEmptyState: MockGlEmptyState,
+ GlEmptyState: stubComponent(GlEmptyState, {
+ template: '<div><slot name="description"></slot></div>',
+ }),
},
});
}
@@ -47,7 +45,7 @@ describe('GroupEmptyState', () => {
});
it('passes the expected props to GlEmptyState', () => {
- expect(wrapper.find(MockGlEmptyState).props()).toMatchSnapshot();
+ expect(wrapper.find(GlEmptyState).props()).toMatchSnapshot();
});
});
});
diff --git a/spec/frontend/notebook/cells/output/html_sanitize_fixtures.js b/spec/frontend/notebook/cells/output/html_sanitize_fixtures.js
index a886715ce4b..0b585ab860b 100644
--- a/spec/frontend/notebook/cells/output/html_sanitize_fixtures.js
+++ b/spec/frontend/notebook/cells/output/html_sanitize_fixtures.js
@@ -1,114 +1,96 @@
+/**
+ * Jupyter notebooks handles the following data types
+ * that are to be handled by `html.vue`
+ *
+ * 'text/html';
+ * 'image/svg+xml';
+ *
+ * This file sets up fixtures for each of these types
+ * NOTE: The inputs are taken directly from data derived from the
+ * jupyter notebook file used to test nbview here:
+ * https://nbviewer.jupyter.org/github/ipython/ipython-in-depth/blob/master/examples/IPython%20Kernel/Rich%20Output.ipynb
+ */
+
export default [
[
- 'protocol-based JS injection: simple, no spaces',
+ 'text/html table',
{
- input: `<a href="javascript:alert('XSS');">foo</a>`,
- output: '<a>foo</a>',
+ input: [
+ '<table>\n',
+ '<tr>\n',
+ '<th>Header 1</th>\n',
+ '<th>Header 2</th>\n',
+ '</tr>\n',
+ '<tr>\n',
+ '<td>row 1, cell 1</td>\n',
+ '<td>row 1, cell 2</td>\n',
+ '</tr>\n',
+ '<tr>\n',
+ '<td>row 2, cell 1</td>\n',
+ '<td>row 2, cell 2</td>\n',
+ '</tr>\n',
+ '</table>',
+ ].join(''),
+ output: '<table>',
},
],
+ // Note: style is sanitized out
[
- 'protocol-based JS injection: simple, spaces before',
+ 'text/html style',
{
- input: `<a href="javascript :alert('XSS');">foo</a>`,
- output: '<a>foo</a>',
+ input: [
+ '<style type="text/css">\n',
+ '\n',
+ 'circle {\n',
+ ' fill: rgb(31, 119, 180);\n',
+ ' fill-opacity: .25;\n',
+ ' stroke: rgb(31, 119, 180);\n',
+ ' stroke-width: 1px;\n',
+ '}\n',
+ '\n',
+ '.leaf circle {\n',
+ ' fill: #ff7f0e;\n',
+ ' fill-opacity: 1;\n',
+ '}\n',
+ '\n',
+ 'text {\n',
+ ' font: 10px sans-serif;\n',
+ '}\n',
+ '\n',
+ '</style>',
+ ].join(''),
+ output: '<!---->',
},
],
+ // Note: iframe is sanitized out
[
- 'protocol-based JS injection: simple, spaces after',
+ 'text/html iframe',
{
- input: `<a href="javascript: alert('XSS');">foo</a>`,
- output: '<a>foo</a>',
+ input: [
+ '\n',
+ ' <iframe\n',
+ ' width="400"\n',
+ ' height="300"\n',
+ ' src="https://www.youtube.com/embed/sjfsUzECqK0"\n',
+ ' frameborder="0"\n',
+ ' allowfullscreen\n',
+ ' ></iframe>\n',
+ ' ',
+ ].join(''),
+ output: '<!---->',
},
],
[
- 'protocol-based JS injection: simple, spaces before and after',
+ 'image/svg+xml',
{
- input: `<a href="javascript : alert('XSS');">foo</a>`,
- output: '<a>foo</a>',
+ input: [
+ '<svg height="115.02pt" id="svg2" version="1.0" width="388.84pt" xmlns="http://www.w3.org/2000/svg">\n',
+ ' <g>\n',
+ ' <path d="M 184.61344,61.929363 C 184.61344,47.367213 180.46118,39.891193 172.15666,39.481813" style="fill:#646464;fill-opacity:1"/>\n',
+ ' </g>\n',
+ '</svg>',
+ ].join(),
+ output: '<svg height="115.02pt" id="svg2"',
},
],
- [
- 'protocol-based JS injection: preceding colon',
- {
- input: `<a href=":javascript:alert('XSS');">foo</a>`,
- output: '<a>foo</a>',
- },
- ],
- [
- 'protocol-based JS injection: UTF-8 encoding',
- {
- input: '<a href="javascript&#58;">foo</a>',
- output: '<a>foo</a>',
- },
- ],
- [
- 'protocol-based JS injection: long UTF-8 encoding',
- {
- input: '<a href="javascript&#0058;">foo</a>',
- output: '<a>foo</a>',
- },
- ],
- [
- 'protocol-based JS injection: long UTF-8 encoding without semicolons',
- {
- input:
- '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
- output: '<a>foo</a>',
- },
- ],
- [
- 'protocol-based JS injection: hex encoding',
- {
- input: '<a href="javascript&#x3A;">foo</a>',
- output: '<a>foo</a>',
- },
- ],
- [
- 'protocol-based JS injection: long hex encoding',
- {
- input: '<a href="javascript&#x003A;">foo</a>',
- output: '<a>foo</a>',
- },
- ],
- [
- 'protocol-based JS injection: hex encoding without semicolons',
- {
- input:
- '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
- output: '<a>foo</a>',
- },
- ],
- [
- 'protocol-based JS injection: null char',
- {
- input: '<a href=java\u0000script:alert("XSS")>foo</a>',
- output: '<a>foo</a>',
- },
- ],
- [
- 'protocol-based JS injection: invalid URL char',
- { input: '<img src=javascript:alert("XSS")>', output: '<img>' },
- ],
- [
- 'protocol-based JS injection: Unicode',
- {
- input: `<a href="\u0001java\u0003script:alert('XSS')">foo</a>`,
- output: '<a>foo</a>',
- },
- ],
- [
- 'protocol-based JS injection: spaces and entities',
- {
- input: `<a href=" &#14; javascript:alert('XSS');">foo</a>`,
- output: '<a>foo</a>',
- },
- ],
- [
- 'img on error',
- {
- input: '<img src="x" onerror="alert(document.domain)" />',
- output: '<img src="x">',
- },
- ],
- ['style tags are removed', { input: '<style>.foo {}</style> Foo', output: 'Foo' }],
];
diff --git a/spec/frontend/notebook/cells/output/html_spec.js b/spec/frontend/notebook/cells/output/html_spec.js
index 48d62d74a50..b94ce7c684d 100644
--- a/spec/frontend/notebook/cells/output/html_spec.js
+++ b/spec/frontend/notebook/cells/output/html_spec.js
@@ -1,26 +1,21 @@
-import Vue from 'vue';
-import htmlOutput from '~/notebook/cells/output/html.vue';
+import { mount } from '@vue/test-utils';
+import HtmlOutput from '~/notebook/cells/output/html.vue';
import sanitizeTests from './html_sanitize_fixtures';
describe('html output cell', () => {
function createComponent(rawCode) {
- const Component = Vue.extend(htmlOutput);
-
- return new Component({
+ return mount(HtmlOutput, {
propsData: {
rawCode,
count: 0,
index: 0,
},
- }).$mount();
+ });
}
it.each(sanitizeTests)('sanitizes output for: %p', (name, { input, output }) => {
const vm = createComponent(input);
- const outputEl = [...vm.$el.querySelectorAll('div')].pop();
-
- expect(outputEl.innerHTML).toEqual(output);
- vm.$destroy();
+ expect(vm.html()).toContain(output);
});
});
diff --git a/spec/frontend/notebook/lib/highlight_spec.js b/spec/frontend/notebook/lib/highlight_spec.js
index d71c5718858..944ccd6aa9f 100644
--- a/spec/frontend/notebook/lib/highlight_spec.js
+++ b/spec/frontend/notebook/lib/highlight_spec.js
@@ -9,7 +9,7 @@ describe('Highlight library', () => {
const el = document.createElement('div');
el.innerHTML = Prism.highlight('console.log("a");', Prism.languages.javascript);
- expect(el.querySelector('.s')).not.toBeNull();
- expect(el.querySelector('.nf')).not.toBeNull();
+ expect(el.querySelector('.string')).not.toBeNull();
+ expect(el.querySelector('.function')).not.toBeNull();
});
});
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index 59fa7b372ed..fca1beca999 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -1,18 +1,20 @@
-import $ from 'jquery';
-import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import Autosize from 'autosize';
-import { trimText } from 'helpers/text_helper';
+import { deprecatedCreateFlash as flash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import createStore from '~/notes/stores';
import CommentForm from '~/notes/components/comment_form.vue';
import * as constants from '~/notes/constants';
+import eventHub from '~/notes/event_hub';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
-import { keyboardDownEvent } from '../../issue_show/helpers';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data';
jest.mock('autosize');
jest.mock('~/commons/nav/user_merge_requests');
+jest.mock('~/flash');
jest.mock('~/gl_form');
describe('issue_comment_form component', () => {
@@ -20,17 +22,33 @@ describe('issue_comment_form component', () => {
let wrapper;
let axiosMock;
- const setupStore = (userData, noteableData) => {
- store.dispatch('setUserData', userData);
+ const findCloseReopenButton = () => wrapper.find('[data-testid="close-reopen-button"]');
+
+ const findCommentButton = () => wrapper.find('[data-testid="comment-button"]');
+
+ const findTextArea = () => wrapper.find('[data-testid="comment-field"]');
+
+ const mountComponent = ({
+ initialData = {},
+ noteableType = 'Issue',
+ noteableData = noteableDataMock,
+ notesData = notesDataMock,
+ userData = userDataMock,
+ mountFunction = shallowMount,
+ } = {}) => {
store.dispatch('setNoteableData', noteableData);
- store.dispatch('setNotesData', notesDataMock);
- };
+ store.dispatch('setNotesData', notesData);
+ store.dispatch('setUserData', userData);
- const mountComponent = (noteableType = 'issue') => {
- wrapper = mount(CommentForm, {
+ wrapper = mountFunction(CommentForm, {
propsData: {
noteableType,
},
+ data() {
+ return {
+ ...initialData,
+ };
+ },
store,
});
};
@@ -46,168 +64,157 @@ describe('issue_comment_form component', () => {
});
describe('user is logged in', () => {
- beforeEach(() => {
- setupStore(userDataMock, noteableDataMock);
-
- mountComponent();
- });
+ describe('avatar', () => {
+ it('should render user avatar with link', () => {
+ mountComponent({ mountFunction: mount });
- it('should render user avatar with link', () => {
- expect(wrapper.find('.timeline-icon .user-avatar-link').attributes('href')).toEqual(
- userDataMock.path,
- );
+ expect(wrapper.find(UserAvatarLink).attributes('href')).toBe(userDataMock.path);
+ });
});
describe('handleSave', () => {
it('should request to save note when note is entered', () => {
- wrapper.vm.note = 'hello world';
- jest.spyOn(wrapper.vm, 'saveNote').mockReturnValue(new Promise(() => {}));
+ mountComponent({ mountFunction: mount, initialData: { note: 'hello world' } });
+
+ jest.spyOn(wrapper.vm, 'saveNote').mockResolvedValue();
jest.spyOn(wrapper.vm, 'resizeTextarea');
jest.spyOn(wrapper.vm, 'stopPolling');
- wrapper.vm.handleSave();
+ findCloseReopenButton().trigger('click');
- expect(wrapper.vm.isSubmitting).toEqual(true);
- expect(wrapper.vm.note).toEqual('');
+ expect(wrapper.vm.isSubmitting).toBe(true);
+ expect(wrapper.vm.note).toBe('');
expect(wrapper.vm.saveNote).toHaveBeenCalled();
expect(wrapper.vm.stopPolling).toHaveBeenCalled();
expect(wrapper.vm.resizeTextarea).toHaveBeenCalled();
});
it('should toggle issue state when no note', () => {
+ mountComponent({ mountFunction: mount });
+
jest.spyOn(wrapper.vm, 'toggleIssueState');
- wrapper.vm.handleSave();
+ findCloseReopenButton().trigger('click');
expect(wrapper.vm.toggleIssueState).toHaveBeenCalled();
});
- it('should disable action button while submitting', done => {
+ it('should disable action button while submitting', async () => {
+ mountComponent({ mountFunction: mount, initialData: { note: 'hello world' } });
+
const saveNotePromise = Promise.resolve();
- wrapper.vm.note = 'hello world';
+
jest.spyOn(wrapper.vm, 'saveNote').mockReturnValue(saveNotePromise);
jest.spyOn(wrapper.vm, 'stopPolling');
- const actionButton = wrapper.find('.js-action-button');
-
- wrapper.vm.handleSave();
-
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(actionButton.vm.disabled).toBeTruthy();
- })
- .then(saveNotePromise)
- .then(wrapper.vm.$nextTick)
- .then(() => {
- expect(actionButton.vm.disabled).toBeFalsy();
- })
- .then(done)
- .catch(done.fail);
+ const actionButton = findCloseReopenButton();
+
+ await actionButton.trigger('click');
+
+ expect(actionButton.props('disabled')).toBe(true);
+
+ await saveNotePromise;
+
+ await nextTick();
+
+ expect(actionButton.props('disabled')).toBe(false);
});
});
describe('textarea', () => {
- it('should render textarea with placeholder', () => {
- expect(wrapper.find('.js-main-target-form textarea').attributes('placeholder')).toEqual(
- 'Write a comment or drag your files here…',
- );
- });
+ describe('general', () => {
+ it('should render textarea with placeholder', () => {
+ mountComponent({ mountFunction: mount });
- it('should make textarea disabled while requesting', done => {
- const $submitButton = $(wrapper.find('.js-comment-submit-button').element);
- wrapper.vm.note = 'hello world';
- jest.spyOn(wrapper.vm, 'stopPolling');
- jest.spyOn(wrapper.vm, 'saveNote').mockReturnValue(new Promise(() => {}));
+ expect(findTextArea().attributes('placeholder')).toBe(
+ 'Write a comment or drag your files here…',
+ );
+ });
- wrapper.vm.$nextTick(() => {
- // Wait for wrapper.vm.note change triggered. It should enable $submitButton.
- $submitButton.trigger('click');
+ it('should make textarea disabled while requesting', async () => {
+ mountComponent({ mountFunction: mount });
- wrapper.vm.$nextTick(() => {
- // Wait for wrapper.isSubmitting triggered. It should disable textarea.
- expect(wrapper.find('.js-main-target-form textarea').attributes('disabled')).toBe(
- 'disabled',
- );
- done();
- });
+ jest.spyOn(wrapper.vm, 'stopPolling');
+ jest.spyOn(wrapper.vm, 'saveNote').mockResolvedValue();
+
+ await wrapper.setData({ note: 'hello world' });
+
+ await findCommentButton().trigger('click');
+
+ expect(findTextArea().attributes('disabled')).toBe('disabled');
});
- });
- it('should support quick actions', () => {
- expect(
- wrapper.find('.js-main-target-form textarea').attributes('data-supports-quick-actions'),
- ).toBe('true');
- });
+ it('should support quick actions', () => {
+ mountComponent({ mountFunction: mount });
- it('should link to markdown docs', () => {
- const { markdownDocsPath } = notesDataMock;
+ expect(findTextArea().attributes('data-supports-quick-actions')).toBe('true');
+ });
- expect(
- wrapper
- .find(`a[href="${markdownDocsPath}"]`)
- .text()
- .trim(),
- ).toEqual('Markdown');
- });
+ it('should link to markdown docs', () => {
+ mountComponent({ mountFunction: mount });
- it('should link to quick actions docs', () => {
- const { quickActionsDocsPath } = notesDataMock;
+ const { markdownDocsPath } = notesDataMock;
- expect(
- wrapper
- .find(`a[href="${quickActionsDocsPath}"]`)
- .text()
- .trim(),
- ).toEqual('quick actions');
- });
+ expect(wrapper.find(`a[href="${markdownDocsPath}"]`).text()).toBe('Markdown');
+ });
+
+ it('should link to quick actions docs', () => {
+ mountComponent({ mountFunction: mount });
+
+ const { quickActionsDocsPath } = notesDataMock;
+
+ expect(wrapper.find(`a[href="${quickActionsDocsPath}"]`).text()).toBe('quick actions');
+ });
+
+ it('should resize textarea after note discarded', async () => {
+ mountComponent({ mountFunction: mount, initialData: { note: 'foo' } });
- it('should resize textarea after note discarded', done => {
- jest.spyOn(wrapper.vm, 'discard');
+ jest.spyOn(wrapper.vm, 'discard');
- wrapper.vm.note = 'foo';
- wrapper.vm.discard();
+ wrapper.vm.discard();
+
+ await nextTick();
- wrapper.vm.$nextTick(() => {
expect(Autosize.update).toHaveBeenCalled();
- done();
});
});
describe('edit mode', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
it('should enter edit mode when arrow up is pressed', () => {
jest.spyOn(wrapper.vm, 'editCurrentUserLastNote');
- wrapper.find('.js-main-target-form textarea').value = 'Foo';
- wrapper
- .find('.js-main-target-form textarea')
- .element.dispatchEvent(keyboardDownEvent(38, true));
+
+ findTextArea().trigger('keydown.up');
expect(wrapper.vm.editCurrentUserLastNote).toHaveBeenCalled();
});
it('inits autosave', () => {
expect(wrapper.vm.autosave).toBeDefined();
- expect(wrapper.vm.autosave.key).toEqual(`autosave/Note/Issue/${noteableDataMock.id}`);
+ expect(wrapper.vm.autosave.key).toBe(`autosave/Note/Issue/${noteableDataMock.id}`);
});
});
describe('event enter', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
it('should save note when cmd+enter is pressed', () => {
jest.spyOn(wrapper.vm, 'handleSave');
- wrapper.find('.js-main-target-form textarea').value = 'Foo';
- wrapper
- .find('.js-main-target-form textarea')
- .element.dispatchEvent(keyboardDownEvent(13, true));
+
+ findTextArea().trigger('keydown.enter', { metaKey: true });
expect(wrapper.vm.handleSave).toHaveBeenCalled();
});
it('should save note when ctrl+enter is pressed', () => {
jest.spyOn(wrapper.vm, 'handleSave');
- wrapper.find('.js-main-target-form textarea').value = 'Foo';
- wrapper
- .find('.js-main-target-form textarea')
- .element.dispatchEvent(keyboardDownEvent(13, false, true));
+
+ findTextArea().trigger('keydown.enter', { ctrlKey: true });
expect(wrapper.vm.handleSave).toHaveBeenCalled();
});
@@ -216,137 +223,187 @@ describe('issue_comment_form component', () => {
describe('actions', () => {
it('should be possible to close the issue', () => {
- expect(
- wrapper
- .find('.btn-comment-and-close')
- .text()
- .trim(),
- ).toEqual('Close issue');
+ mountComponent();
+
+ expect(findCloseReopenButton().text()).toBe('Close issue');
});
it('should render comment button as disabled', () => {
- expect(wrapper.find('.js-comment-submit-button').attributes('disabled')).toEqual(
- 'disabled',
- );
+ mountComponent();
+
+ expect(findCommentButton().props('disabled')).toBe(true);
});
- it('should enable comment button if it has note', done => {
- wrapper.vm.note = 'Foo';
- wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.js-comment-submit-button').attributes('disabled')).toBeFalsy();
- done();
- });
+ it('should enable comment button if it has note', async () => {
+ mountComponent();
+
+ await wrapper.setData({ note: 'Foo' });
+
+ expect(findCommentButton().props('disabled')).toBe(false);
});
- it('should update buttons texts when it has note', done => {
- wrapper.vm.note = 'Foo';
- wrapper.vm.$nextTick(() => {
- expect(
- wrapper
- .find('.btn-comment-and-close')
- .text()
- .trim(),
- ).toEqual('Comment & close issue');
-
- done();
- });
+ it('should update buttons texts when it has note', () => {
+ mountComponent({ initialData: { note: 'Foo' } });
+
+ expect(findCloseReopenButton().text()).toBe('Comment & close issue');
});
- it('updates button text with noteable type', done => {
- wrapper.setProps({ noteableType: constants.MERGE_REQUEST_NOTEABLE_TYPE });
-
- wrapper.vm.$nextTick(() => {
- expect(
- wrapper
- .find('.btn-comment-and-close')
- .text()
- .trim(),
- ).toEqual('Close merge request');
- done();
- });
+ it('updates button text with noteable type', () => {
+ mountComponent({ noteableType: constants.MERGE_REQUEST_NOTEABLE_TYPE });
+
+ expect(findCloseReopenButton().text()).toBe('Close merge request');
});
describe('when clicking close/reopen button', () => {
- it('should disable button and show a loading spinner', () => {
- const toggleStateButton = wrapper.find('.js-action-button');
+ it('should show a loading spinner', async () => {
+ mountComponent({
+ noteableType: constants.MERGE_REQUEST_NOTEABLE_TYPE,
+ mountFunction: mount,
+ });
- toggleStateButton.trigger('click');
+ await findCloseReopenButton().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(toggleStateButton.element.disabled).toEqual(true);
- expect(toggleStateButton.props('loading')).toBe(true);
- });
+ expect(findCloseReopenButton().props('loading')).toBe(true);
});
});
describe('when toggling state', () => {
- it('should update MR count', done => {
- jest.spyOn(wrapper.vm, 'closeIssue').mockResolvedValue();
+ describe('when issue', () => {
+ it('emits event to toggle state', () => {
+ mountComponent({ mountFunction: mount });
+
+ jest.spyOn(eventHub, '$emit');
+
+ findCloseReopenButton().trigger('click');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('toggle.issuable.state');
+ });
+ });
+
+ describe.each`
+ type | noteableType
+ ${'merge request'} | ${'MergeRequest'}
+ ${'epic'} | ${'Epic'}
+ `('when $type', ({ type, noteableType }) => {
+ describe('when open', () => {
+ it(`makes an API call to open it`, () => {
+ mountComponent({
+ noteableType,
+ noteableData: { ...noteableDataMock, state: constants.OPENED },
+ mountFunction: mount,
+ });
+
+ jest.spyOn(wrapper.vm, 'closeIssuable').mockResolvedValue();
+
+ findCloseReopenButton().trigger('click');
+
+ expect(wrapper.vm.closeIssuable).toHaveBeenCalled();
+ });
+
+ it(`shows an error when the API call fails`, async () => {
+ mountComponent({
+ noteableType,
+ noteableData: { ...noteableDataMock, state: constants.OPENED },
+ mountFunction: mount,
+ });
+
+ jest.spyOn(wrapper.vm, 'closeIssuable').mockRejectedValue();
+
+ await findCloseReopenButton().trigger('click');
+
+ await wrapper.vm.$nextTick;
+
+ expect(flash).toHaveBeenCalledWith(
+ `Something went wrong while closing the ${type}. Please try again later.`,
+ );
+ });
+ });
+
+ describe('when closed', () => {
+ it('makes an API call to close it', () => {
+ mountComponent({
+ noteableType,
+ noteableData: { ...noteableDataMock, state: constants.CLOSED },
+ mountFunction: mount,
+ });
+
+ jest.spyOn(wrapper.vm, 'reopenIssuable').mockResolvedValue();
- wrapper.vm.toggleIssueState();
+ findCloseReopenButton().trigger('click');
- wrapper.vm.$nextTick(() => {
- expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
+ expect(wrapper.vm.reopenIssuable).toHaveBeenCalled();
+ });
+ });
+
+ it(`shows an error when the API call fails`, async () => {
+ mountComponent({
+ noteableType,
+ noteableData: { ...noteableDataMock, state: constants.CLOSED },
+ mountFunction: mount,
+ });
+
+ jest.spyOn(wrapper.vm, 'reopenIssuable').mockRejectedValue();
+
+ await findCloseReopenButton().trigger('click');
- done();
+ await wrapper.vm.$nextTick;
+
+ expect(flash).toHaveBeenCalledWith(
+ `Something went wrong while reopening the ${type}. Please try again later.`,
+ );
});
});
+
+ it('when merge request, should update MR count', async () => {
+ mountComponent({
+ noteableType: constants.MERGE_REQUEST_NOTEABLE_TYPE,
+ mountFunction: mount,
+ });
+
+ jest.spyOn(wrapper.vm, 'closeIssuable').mockResolvedValue();
+
+ await findCloseReopenButton().trigger('click');
+
+ expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
+ });
});
});
describe('issue is confidential', () => {
- it('shows information warning', done => {
- store.dispatch('setNoteableData', Object.assign(noteableDataMock, { confidential: true }));
- wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.confidential-issue-warning')).toBeDefined();
- done();
+ it('shows information warning', () => {
+ mountComponent({
+ noteableData: { ...noteableDataMock, confidential: true },
+ mountFunction: mount,
});
+
+ expect(wrapper.find('[data-testid="confidential-warning"]').exists()).toBe(true);
});
});
});
describe('user is not logged in', () => {
beforeEach(() => {
- setupStore(null, loggedOutnoteableData);
-
- mountComponent();
+ mountComponent({ userData: null, noteableData: loggedOutnoteableData, mountFunction: mount });
});
it('should render signed out widget', () => {
- expect(trimText(wrapper.text())).toEqual('Please register or sign in to reply');
+ expect(wrapper.text()).toBe('Please register or sign in to reply');
});
it('should not render submission form', () => {
- expect(wrapper.find('textarea').exists()).toBe(false);
+ expect(findTextArea().exists()).toBe(false);
});
});
- describe('when issuable is open', () => {
- beforeEach(() => {
- setupStore(userDataMock, noteableDataMock);
- });
-
- it.each([['opened', 'warning'], ['reopened', 'warning']])(
- 'when %i, it changes the variant of the btn to %i',
- (a, expected) => {
- store.state.noteableData.state = a;
-
- mountComponent();
-
- expect(wrapper.find('.js-action-button').props('variant')).toBe(expected);
- },
- );
- });
-
- describe('when issuable is not open', () => {
- beforeEach(() => {
- setupStore(userDataMock, noteableDataMock);
-
- mountComponent();
- });
+ describe('close/reopen button variants', () => {
+ it.each([
+ [constants.OPENED, 'warning'],
+ [constants.REOPENED, 'warning'],
+ [constants.CLOSED, 'default'],
+ ])('when %s, the variant of the btn is %s', (state, expected) => {
+ mountComponent({ noteableData: { ...noteableDataMock, state } });
- it('should render the "default" variant of the button', () => {
- expect(wrapper.find('.js-action-button').props('variant')).toBe('warning');
+ expect(findCloseReopenButton().props('variant')).toBe(expected);
});
});
});
diff --git a/spec/frontend/notes/components/multiline_comment_utils_spec.js b/spec/frontend/notes/components/multiline_comment_utils_spec.js
index af4394cc648..99b33e7cd5f 100644
--- a/spec/frontend/notes/components/multiline_comment_utils_spec.js
+++ b/spec/frontend/notes/components/multiline_comment_utils_spec.js
@@ -34,8 +34,17 @@ describe('Multiline comment utilities', () => {
expect(getSymbol(type)).toEqual(result);
});
});
- describe('getCommentedLines', () => {
- const diffLines = [{ line_code: '1' }, { line_code: '2' }, { line_code: '3' }];
+ const inlineDiffLines = [{ line_code: '1' }, { line_code: '2' }, { line_code: '3' }];
+ const parallelDiffLines = inlineDiffLines.map(line => ({
+ left: { ...line },
+ right: { ...line },
+ }));
+
+ describe.each`
+ view | diffLines
+ ${'inline'} | ${inlineDiffLines}
+ ${'parallel'} | ${parallelDiffLines}
+ `('getCommentedLines $view view', ({ diffLines }) => {
it('returns a default object when `selectedCommentPosition` is not provided', () => {
expect(getCommentedLines(undefined, diffLines)).toEqual({ startLine: 4, endLine: 4 });
});
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 69aab0d051e..1c6d0bafda8 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -22,6 +22,10 @@ describe('NoteHeader component', () => {
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
const findConfidentialIndicator = () => wrapper.find('[data-testid="confidentialIndicator"]');
const findSpinner = () => wrapper.find({ ref: 'spinner' });
+ const findAuthorStatus = () => wrapper.find({ ref: 'authorStatus' });
+
+ const statusHtml =
+ '"<span class="user-status-emoji has-tooltip" title="foo bar" data-html="true" data-placement="top"><gl-emoji title="basketball and hoop" data-name="basketball" data-unicode-version="6.0">🏀</gl-emoji></span>"';
const author = {
avatar_url: null,
@@ -30,6 +34,8 @@ describe('NoteHeader component', () => {
path: '/root',
state: 'active',
username: 'root',
+ show_status: true,
+ status_tooltip_html: statusHtml,
status: {
availability: '',
},
@@ -109,6 +115,32 @@ describe('NoteHeader component', () => {
expect(wrapper.find('.js-user-link').text()).toContain('(Busy)');
});
+ it('renders author status', () => {
+ createComponent({ author });
+
+ expect(findAuthorStatus().exists()).toBe(true);
+ });
+
+ it('does not render author status if show_status=false', () => {
+ createComponent({
+ author: { ...author, status: { availability: AVAILABILITY_STATUS.BUSY }, show_status: false },
+ });
+
+ expect(findAuthorStatus().exists()).toBe(false);
+ });
+
+ it('does not render author status if status_tooltip_html=null', () => {
+ createComponent({
+ author: {
+ ...author,
+ status: { availability: AVAILABILITY_STATUS.BUSY },
+ status_tooltip_html: null,
+ },
+ });
+
+ expect(findAuthorStatus().exists()).toBe(false);
+ });
+
it('renders deleted user text if author is not passed as a prop', () => {
createComponent();
@@ -206,13 +238,12 @@ describe('NoteHeader component', () => {
createComponent({
author: {
...author,
- status_tooltip_html:
- '"<span class="user-status-emoji has-tooltip" title="foo bar" data-html="true" data-placement="top"><gl-emoji title="basketball and hoop" data-name="basketball" data-unicode-version="6.0">🏀</gl-emoji></span>"',
+ status_tooltip_html: statusHtml,
},
});
return nextTick().then(() => {
- const authorStatus = wrapper.find({ ref: 'authorStatus' });
+ const authorStatus = findAuthorStatus();
authorStatus.trigger('mouseenter');
expect(authorStatus.find('gl-emoji').attributes('title')).toBeUndefined();
diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js
index d203435e7bf..4114df618e5 100644
--- a/spec/frontend/notes/mixins/discussion_navigation_spec.js
+++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js
@@ -42,6 +42,7 @@ describe('Discussion navigation mixin', () => {
);
jest.spyOn(utils, 'scrollToElementWithContext');
+ jest.spyOn(utils, 'scrollToElement');
expandDiscussion = jest.fn();
const { actions, ...notesRest } = notesModule();
@@ -133,7 +134,7 @@ describe('Discussion navigation mixin', () => {
});
it('scrolls to element', () => {
- expect(utils.scrollToElementWithContext).toHaveBeenCalledWith(
+ expect(utils.scrollToElement).toHaveBeenCalledWith(
findDiscussion('div.discussion', expected),
);
});
@@ -200,7 +201,7 @@ describe('Discussion navigation mixin', () => {
});
it('scrolls to discussion', () => {
- expect(utils.scrollToElementWithContext).toHaveBeenCalledWith(
+ expect(utils.scrollToElement).toHaveBeenCalledWith(
findDiscussion('div.discussion', expected),
);
});
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index 920959f41e7..c9912621785 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -174,10 +174,10 @@ describe('Actions Notes Store', () => {
axiosMock.onAny().reply(200, {});
});
- describe('closeIssue', () => {
+ describe('closeMergeRequest', () => {
it('sets state as closed', done => {
store
- .dispatch('closeIssue', { notesData: { closeIssuePath: '' } })
+ .dispatch('closeIssuable', { notesData: { closeIssuePath: '' } })
.then(() => {
expect(store.state.noteableData.state).toEqual('closed');
expect(store.state.isToggleStateButtonLoading).toEqual(false);
@@ -187,10 +187,10 @@ describe('Actions Notes Store', () => {
});
});
- describe('reopenIssue', () => {
+ describe('reopenMergeRequest', () => {
it('sets state as reopened', done => {
store
- .dispatch('reopenIssue', { notesData: { reopenIssuePath: '' } })
+ .dispatch('reopenIssuable', { notesData: { reopenIssuePath: '' } })
.then(() => {
expect(store.state.noteableData.state).toEqual('reopened');
expect(store.state.isToggleStateButtonLoading).toEqual(false);
@@ -253,30 +253,6 @@ describe('Actions Notes Store', () => {
});
});
- describe('toggleBlockedIssueWarning', () => {
- it('should set issue warning as true', done => {
- testAction(
- actions.toggleBlockedIssueWarning,
- true,
- {},
- [{ type: 'TOGGLE_BLOCKED_ISSUE_WARNING', payload: true }],
- [],
- done,
- );
- });
-
- it('should set issue warning as false', done => {
- testAction(
- actions.toggleBlockedIssueWarning,
- false,
- {},
- [{ type: 'TOGGLE_BLOCKED_ISSUE_WARNING', payload: false }],
- [],
- done,
- );
- });
- });
-
describe('fetchData', () => {
describe('given there are no notes', () => {
const lastFetchedAt = '13579';
@@ -944,10 +920,16 @@ describe('Actions Notes Store', () => {
it('when service success, commits and resolves discussion', done => {
testSubmitSuggestion(done, () => {
expect(commit.mock.calls).toEqual([
+ [mutationTypes.SET_RESOLVING_DISCUSSION, true],
[mutationTypes.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
- expect(dispatch.mock.calls).toEqual([['resolveDiscussion', { discussionId }]]);
+ expect(dispatch.mock.calls).toEqual([
+ ['stopPolling'],
+ ['resolveDiscussion', { discussionId }],
+ ['restartPolling'],
+ ]);
expect(Flash).not.toHaveBeenCalled();
});
});
@@ -958,8 +940,11 @@ describe('Actions Notes Store', () => {
Api.applySuggestion.mockReturnValue(Promise.reject(response));
testSubmitSuggestion(done, () => {
- expect(commit).not.toHaveBeenCalled();
- expect(dispatch).not.toHaveBeenCalled();
+ expect(commit.mock.calls).toEqual([
+ [mutationTypes.SET_RESOLVING_DISCUSSION, true],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, false],
+ ]);
+ expect(dispatch.mock.calls).toEqual([['stopPolling'], ['restartPolling']]);
expect(Flash).toHaveBeenCalledWith(TEST_ERROR_MESSAGE, 'alert', flashContainer);
});
});
@@ -970,8 +955,11 @@ describe('Actions Notes Store', () => {
Api.applySuggestion.mockReturnValue(Promise.reject(response));
testSubmitSuggestion(done, () => {
- expect(commit).not.toHaveBeenCalled();
- expect(dispatch).not.toHaveBeenCalled();
+ expect(commit.mock.calls).toEqual([
+ [mutationTypes.SET_RESOLVING_DISCUSSION, true],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, false],
+ ]);
+ expect(dispatch.mock.calls).toEqual([['stopPolling'], ['restartPolling']]);
expect(Flash).toHaveBeenCalledWith(
'Something went wrong while applying the suggestion. Please try again.',
'alert',
@@ -1015,15 +1003,19 @@ describe('Actions Notes Store', () => {
testSubmitSuggestionBatch(done, () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_APPLYING_BATCH_STATE, true],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, true],
[mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[0]],
[mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[1]],
[mutationTypes.CLEAR_SUGGESTION_BATCH],
[mutationTypes.SET_APPLYING_BATCH_STATE, false],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
expect(dispatch.mock.calls).toEqual([
+ ['stopPolling'],
['resolveDiscussion', { discussionId: discussionIds[0] }],
['resolveDiscussion', { discussionId: discussionIds[1] }],
+ ['restartPolling'],
]);
expect(Flash).not.toHaveBeenCalled();
@@ -1038,10 +1030,12 @@ describe('Actions Notes Store', () => {
testSubmitSuggestionBatch(done, () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_APPLYING_BATCH_STATE, true],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, true],
[mutationTypes.SET_APPLYING_BATCH_STATE, false],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
- expect(dispatch).not.toHaveBeenCalled();
+ expect(dispatch.mock.calls).toEqual([['stopPolling'], ['restartPolling']]);
expect(Flash).toHaveBeenCalledWith(TEST_ERROR_MESSAGE, 'alert', flashContainer);
});
});
@@ -1054,10 +1048,12 @@ describe('Actions Notes Store', () => {
testSubmitSuggestionBatch(done, () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_APPLYING_BATCH_STATE, true],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, true],
[mutationTypes.SET_APPLYING_BATCH_STATE, false],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
- expect(dispatch).not.toHaveBeenCalled();
+ expect(dispatch.mock.calls).toEqual([['stopPolling'], ['restartPolling']]);
expect(Flash).toHaveBeenCalledWith(
'Something went wrong while applying the batch of suggestions. Please try again.',
'alert',
@@ -1072,10 +1068,12 @@ describe('Actions Notes Store', () => {
testSubmitSuggestionBatch(done, () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_APPLYING_BATCH_STATE, true],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, true],
[mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[0]],
[mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[1]],
[mutationTypes.CLEAR_SUGGESTION_BATCH],
[mutationTypes.SET_APPLYING_BATCH_STATE, false],
+ [mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
expect(Flash).not.toHaveBeenCalled();
diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js
index 2618c3a53b8..ec4de925721 100644
--- a/spec/frontend/notes/stores/mutation_spec.js
+++ b/spec/frontend/notes/stores/mutation_spec.js
@@ -377,6 +377,16 @@ describe('Notes Store mutations', () => {
});
});
+ describe('SET_RESOLVING_DISCUSSION', () => {
+ it('should set resolving discussion state', () => {
+ const state = {};
+
+ mutations.SET_RESOLVING_DISCUSSION(state, true);
+
+ expect(state.isResolvingDiscussion).toEqual(true);
+ });
+ });
+
describe('UPDATE_NOTE', () => {
it('should update a note', () => {
const state = {
@@ -687,42 +697,6 @@ describe('Notes Store mutations', () => {
});
});
- describe('TOGGLE_BLOCKED_ISSUE_WARNING', () => {
- it('should set isToggleBlockedIssueWarning as true', () => {
- const state = {
- discussions: [],
- targetNoteHash: null,
- lastFetchedAt: null,
- isToggleStateButtonLoading: false,
- isToggleBlockedIssueWarning: false,
- notesData: {},
- userData: {},
- noteableData: {},
- };
-
- mutations.TOGGLE_BLOCKED_ISSUE_WARNING(state, true);
-
- expect(state.isToggleBlockedIssueWarning).toEqual(true);
- });
-
- it('should set isToggleBlockedIssueWarning as false', () => {
- const state = {
- discussions: [],
- targetNoteHash: null,
- lastFetchedAt: null,
- isToggleStateButtonLoading: false,
- isToggleBlockedIssueWarning: true,
- notesData: {},
- userData: {},
- noteableData: {},
- };
-
- mutations.TOGGLE_BLOCKED_ISSUE_WARNING(state, false);
-
- expect(state.isToggleBlockedIssueWarning).toEqual(false);
- });
- });
-
describe('SET_APPLYING_BATCH_STATE', () => {
const buildDiscussions = suggestionsInfo => {
const suggestions = suggestionsInfo.map(({ suggestionId }) => ({ id: suggestionId }));
diff --git a/spec/frontend/packages/details/components/app_spec.js b/spec/frontend/packages/details/components/app_spec.js
index e82c74e56e5..97df117df0b 100644
--- a/spec/frontend/packages/details/components/app_spec.js
+++ b/spec/frontend/packages/details/components/app_spec.js
@@ -16,6 +16,7 @@ import DependencyRow from '~/packages/details/components/dependency_row.vue';
import PackageHistory from '~/packages/details/components/package_history.vue';
import AdditionalMetadata from '~/packages/details/components/additional_metadata.vue';
import InstallationCommands from '~/packages/details/components/installation_commands.vue';
+import PackageFiles from '~/packages/details/components/package_files.vue';
import {
composerPackage,
@@ -23,7 +24,6 @@ import {
mavenPackage,
mavenFiles,
npmPackage,
- npmFiles,
nugetPackage,
} from '../../mock_data';
@@ -82,8 +82,6 @@ describe('PackagesApp', () => {
const packageTitle = () => wrapper.find(PackageTitle);
const emptyState = () => wrapper.find(GlEmptyState);
- const allFileRows = () => wrapper.findAll('.js-file-row');
- const firstFileDownloadLink = () => wrapper.find('.js-file-download');
const deleteButton = () => wrapper.find('.js-delete-button');
const deleteModal = () => wrapper.find(GlModal);
const modalDeleteButton = () => wrapper.find({ ref: 'modal-delete-button' });
@@ -98,6 +96,7 @@ describe('PackagesApp', () => {
const findPackageHistory = () => wrapper.find(PackageHistory);
const findAdditionalMetadata = () => wrapper.find(AdditionalMetadata);
const findInstallationCommands = () => wrapper.find(InstallationCommands);
+ const findPackageFiles = () => wrapper.find(PackageFiles);
beforeEach(() => {
delete window.location;
@@ -144,28 +143,7 @@ describe('PackagesApp', () => {
it('hides the files table if package type is COMPOSER', () => {
createComponent({ packageEntity: composerPackage });
- expect(allFileRows().exists()).toBe(false);
- });
-
- it('renders a single file for an npm package as they only contain one file', () => {
- createComponent({ packageEntity: npmPackage, packageFiles: npmFiles });
-
- expect(allFileRows()).toExist();
- expect(allFileRows()).toHaveLength(1);
- });
-
- it('renders multiple files for a package that contains more than one file', () => {
- createComponent();
-
- expect(allFileRows()).toExist();
- expect(allFileRows()).toHaveLength(2);
- });
-
- it('allows the user to download a package file by rendering a download link', () => {
- createComponent();
-
- expect(allFileRows()).toExist();
- expect(firstFileDownloadLink().vm.$attrs.href).toContain('download');
+ expect(findPackageFiles().exists()).toBe(false);
});
describe('deleting packages', () => {
@@ -331,7 +309,7 @@ describe('PackagesApp', () => {
it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => {
createComponent({ packageEntity: conanPackage });
- firstFileDownloadLink().vm.$emit('click');
+ findPackageFiles().vm.$emit('download-file');
expect(eventSpy).toHaveBeenCalledWith(
category,
TrackingActions.PULL_PACKAGE,
diff --git a/spec/frontend/packages/details/components/package_files_spec.js b/spec/frontend/packages/details/components/package_files_spec.js
new file mode 100644
index 00000000000..813a2170154
--- /dev/null
+++ b/spec/frontend/packages/details/components/package_files_spec.js
@@ -0,0 +1,131 @@
+import { mount } from '@vue/test-utils';
+import stubChildren from 'helpers/stub_children';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import component from '~/packages/details/components/package_files.vue';
+
+import { npmFiles, mavenFiles } from '../../mock_data';
+
+describe('Package Files', () => {
+ let wrapper;
+
+ const findAllRows = () => wrapper.findAll('[data-testid="file-row"');
+ const findFirstRow = () => findAllRows().at(0);
+ const findFirstRowDownloadLink = () => findFirstRow().find('[data-testid="download-link"');
+ const findFirstRowCommitLink = () => findFirstRow().find('[data-testid="commit-link"');
+ const findFirstRowFileIcon = () => findFirstRow().find(FileIcon);
+ const findFirstRowCreatedAt = () => findFirstRow().find(TimeAgoTooltip);
+
+ const createComponent = (packageFiles = npmFiles) => {
+ wrapper = mount(component, {
+ propsData: {
+ packageFiles,
+ },
+ stubs: {
+ ...stubChildren(component),
+ GlTable: false,
+ GlLink: '<div><slot></slot></div>',
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('rows', () => {
+ it('renders a single file for an npm package', () => {
+ createComponent();
+
+ expect(findAllRows()).toHaveLength(1);
+ });
+
+ it('renders multiple files for a package that contains more than one file', () => {
+ createComponent(mavenFiles);
+
+ expect(findAllRows()).toHaveLength(2);
+ });
+ });
+
+ describe('link', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstRowDownloadLink().exists()).toBe(true);
+ });
+
+ it('has the correct attrs bound', () => {
+ createComponent();
+
+ expect(findFirstRowDownloadLink().attributes('href')).toBe(npmFiles[0].download_path);
+ });
+
+ it('emits "download-file" event on click', () => {
+ createComponent();
+
+ findFirstRowDownloadLink().vm.$emit('click');
+
+ expect(wrapper.emitted('download-file')).toEqual([[]]);
+ });
+ });
+
+ describe('file-icon', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstRowFileIcon().exists()).toBe(true);
+ });
+
+ it('has the correct props bound', () => {
+ createComponent();
+
+ expect(findFirstRowFileIcon().props('fileName')).toBe(npmFiles[0].file_name);
+ });
+ });
+
+ describe('time-ago tooltip', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstRowCreatedAt().exists()).toBe(true);
+ });
+
+ it('has the correct props bound', () => {
+ createComponent();
+
+ expect(findFirstRowCreatedAt().props('time')).toBe(npmFiles[0].created_at);
+ });
+ });
+
+ describe('commit', () => {
+ describe('when package file has a pipeline associated', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstRowCommitLink().exists()).toBe(true);
+ });
+
+ it('the link points to the commit url', () => {
+ createComponent();
+
+ expect(findFirstRowCommitLink().attributes('href')).toBe(
+ npmFiles[0].pipelines[0].project.commit_url,
+ );
+ });
+
+ it('the text is git_commit_message', () => {
+ createComponent();
+
+ expect(findFirstRowCommitLink().text()).toBe(npmFiles[0].pipelines[0].git_commit_message);
+ });
+ });
+ describe('when package file has no pipeline associated', () => {
+ it('does not exist', () => {
+ createComponent(mavenFiles);
+
+ expect(findFirstRowCommitLink().exists()).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages/details/components/package_history_spec.js b/spec/frontend/packages/details/components/package_history_spec.js
index f745a457b0a..c43ac9b9c40 100644
--- a/spec/frontend/packages/details/components/package_history_spec.js
+++ b/spec/frontend/packages/details/components/package_history_spec.js
@@ -1,7 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf } from '@gitlab/ui';
+import { stubComponent } from 'helpers/stub_component';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
+import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants';
import component from '~/packages/details/components/package_history.vue';
import { mavenPackage, mockPipelineInfo } from '../../mock_data';
@@ -13,14 +15,16 @@ describe('Package History', () => {
packageEntity: { ...mavenPackage },
};
+ const createPipelines = amount =>
+ [...Array(amount)].map((x, index) => ({ ...mockPipelineInfo, id: index + 1 }));
+
const mountComponent = props => {
wrapper = shallowMount(component, {
propsData: { ...defaultProps, ...props },
stubs: {
- HistoryItem: {
- props: HistoryItem.props,
+ HistoryItem: stubComponent(HistoryItem, {
template: '<div data-testid="history-element"><slot></slot></div>',
- },
+ }),
GlSprintf,
},
});
@@ -56,55 +60,58 @@ describe('Package History', () => {
expect.arrayContaining(['timeline', 'main-notes-list', 'notes']),
);
});
-
describe.each`
- name | icon | text | timeAgoTooltip | link
- ${'created-on'} | ${'clock'} | ${'Test package version 1.0.0 was created'} | ${mavenPackage.created_at} | ${null}
- ${'updated-at'} | ${'pencil'} | ${'Test package version 1.0.0 was updated'} | ${mavenPackage.updated_at} | ${null}
- ${'commit'} | ${'commit'} | ${'Commit sha-baz on branch branch-name'} | ${null} | ${mockPipelineInfo.project.commit_url}
- ${'pipeline'} | ${'pipeline'} | ${'Pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${mockPipelineInfo.project.pipeline_url}
- ${'published'} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null}
- `('history element $name', ({ name, icon, text, timeAgoTooltip, link }) => {
- let element;
-
- beforeEach(() => {
- mountComponent({ packageEntity: { ...mavenPackage, pipeline: mockPipelineInfo } });
- element = findHistoryElement(name);
- });
-
- it('has the correct icon', () => {
- expect(element.props('icon')).toBe(icon);
- });
-
- it('has the correct text', () => {
- expect(element.text()).toBe(text);
- });
-
- it('time-ago tooltip', () => {
- const timeAgo = findElementTimeAgo(element);
- const exist = Boolean(timeAgoTooltip);
-
- expect(timeAgo.exists()).toBe(exist);
- if (exist) {
- expect(timeAgo.props('time')).toBe(timeAgoTooltip);
- }
- });
-
- it('link', () => {
- const linkElement = findElementLink(element);
- const exist = Boolean(link);
-
- expect(linkElement.exists()).toBe(exist);
- if (exist) {
- expect(linkElement.attributes('href')).toBe(link);
- }
- });
- });
-
- describe('when pipelineInfo is missing', () => {
- it.each(['commit', 'pipeline'])('%s history element is hidden', name => {
- mountComponent();
- expect(findHistoryElement(name).exists()).toBe(false);
- });
- });
+ name | amount | icon | text | timeAgoTooltip | link
+ ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'Test package version 1.0.0 was first created'} | ${mavenPackage.created_at} | ${null}
+ ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit #sha-baz on branch branch-name'} | ${null} | ${mockPipelineInfo.project.commit_url}
+ ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${mockPipelineInfo.project.pipeline_url}
+ ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null}
+ ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null}
+ ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null}
+ ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit #sha-baz on branch branch-name, built by pipeline #3, and published to the registry'} | ${mavenPackage.created_at} | ${mockPipelineInfo.project.commit_url}
+ `(
+ 'with $amount pipelines history element $name',
+ ({ name, icon, text, timeAgoTooltip, link, amount }) => {
+ let element;
+
+ beforeEach(() => {
+ mountComponent({
+ packageEntity: { ...mavenPackage, pipelines: createPipelines(amount) },
+ });
+ element = findHistoryElement(name);
+ });
+
+ it('exists', () => {
+ expect(element.exists()).toBe(true);
+ });
+
+ it('has the correct icon', () => {
+ expect(element.props('icon')).toBe(icon);
+ });
+
+ it('has the correct text', () => {
+ expect(element.text()).toBe(text);
+ });
+
+ it('time-ago tooltip', () => {
+ const timeAgo = findElementTimeAgo(element);
+ const exist = Boolean(timeAgoTooltip);
+
+ expect(timeAgo.exists()).toBe(exist);
+ if (exist) {
+ expect(timeAgo.props('time')).toBe(timeAgoTooltip);
+ }
+ });
+
+ it('link', () => {
+ const linkElement = findElementLink(element);
+ const exist = Boolean(link);
+
+ expect(linkElement.exists()).toBe(exist);
+ if (exist) {
+ expect(linkElement.attributes('href')).toBe(link);
+ }
+ });
+ },
+ );
});
diff --git a/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
index d27038e765f..c51130dae00 100644
--- a/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
+++ b/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
@@ -202,6 +202,67 @@ exports[`packages_list_app renders 1`] = `
</b-tab-stub>
<b-tab-stub
tag="div"
+ title="Generic"
+ titlelinkclass="gl-tab-nav-item"
+ >
+ <template>
+ <div>
+ <section
+ class="row empty-state text-center"
+ >
+ <div
+ class="col-12"
+ >
+ <div
+ class="svg-250 svg-content"
+ >
+ <img
+ alt="There are no Generic packages yet"
+ class="gl-max-w-full"
+ src="helpSvg"
+ />
+ </div>
+ </div>
+
+ <div
+ class="col-12"
+ >
+ <div
+ class="text-content gl-mx-auto gl-my-0 gl-p-5"
+ >
+ <h1
+ class="h4"
+ >
+ There are no Generic packages yet
+ </h1>
+
+ <p>
+ Learn how to
+ <b-link-stub
+ class="gl-link"
+ event="click"
+ href="helpUrl"
+ routertag="a"
+ target="_blank"
+ >
+ publish and share your packages
+ </b-link-stub>
+ with GitLab.
+ </p>
+
+ <div>
+ <!---->
+
+ <!---->
+ </div>
+ </div>
+ </div>
+ </section>
+ </div>
+ </template>
+ </b-tab-stub>
+ <b-tab-stub
+ tag="div"
title="Maven"
titlelinkclass="gl-tab-nav-item"
>
diff --git a/spec/frontend/packages/mock_data.js b/spec/frontend/packages/mock_data.js
index d7494bf85d0..fbc167729d9 100644
--- a/spec/frontend/packages/mock_data.js
+++ b/spec/frontend/packages/mock_data.js
@@ -76,6 +76,9 @@ export const npmFiles = [
id: 2,
size: 200,
download_path: '/-/package_files/2/download',
+ pipelines: [
+ { id: 1, project: { commit_url: 'http://foo.bar' }, git_commit_message: 'foo bar baz?' },
+ ],
},
];
diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap
deleted file mode 100644
index dbf8caae357..00000000000
--- a/spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap
+++ /dev/null
@@ -1,34 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`User Operation confirmation modal renders modal with form included 1`] = `
-<gl-modal-stub
- modalclass=""
- modalid="user-operation-modal"
- ok-title="action"
- ok-variant="warning"
- size="md"
- title="title"
- titletag="h4"
->
- <form
- action="/url"
- method="post"
- >
- <span>
- content
- </span>
-
- <input
- name="_method"
- type="hidden"
- value="method"
- />
-
- <input
- name="authenticity_token"
- type="hidden"
- value="csrf"
- />
- </form>
-</gl-modal-stub>
-`;
diff --git a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js
index 3d615d9d05f..6df2efd624d 100644
--- a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js
+++ b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js
@@ -14,21 +14,18 @@ describe('Users admin page Modal Manager', () => {
},
};
- const actionModals = {
- action1: ModalStub,
- action2: ModalStub,
- };
-
let wrapper;
const createComponent = (props = {}) => {
wrapper = mount(UserModalManager, {
propsData: {
- actionModals,
modalConfiguration,
csrfToken: 'dummyCSRF',
...props,
},
+ stubs: {
+ DeleteUserModal: ModalStub,
+ },
});
};
@@ -43,11 +40,6 @@ describe('Users admin page Modal Manager', () => {
expect(wrapper.find({ ref: 'modal' }).exists()).toBeFalsy();
});
- it('throws if non-existing action is requested', () => {
- createComponent();
- expect(() => wrapper.vm.show({ glModalAction: 'non-existing' })).toThrow();
- });
-
it('throws if action has no proper configuration', () => {
createComponent({
modalConfiguration: {},
diff --git a/spec/frontend/pages/admin/users/components/user_operation_confirmation_modal_spec.js b/spec/frontend/pages/admin/users/components/user_operation_confirmation_modal_spec.js
deleted file mode 100644
index f3a37a255cd..00000000000
--- a/spec/frontend/pages/admin/users/components/user_operation_confirmation_modal_spec.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlModal } from '@gitlab/ui';
-import UserOperationConfirmationModal from '~/pages/admin/users/components/user_operation_confirmation_modal.vue';
-
-describe('User Operation confirmation modal', () => {
- let wrapper;
-
- const createComponent = (props = {}) => {
- wrapper = shallowMount(UserOperationConfirmationModal, {
- propsData: {
- title: 'title',
- content: 'content',
- action: 'action',
- url: '/url',
- username: 'username',
- csrfToken: 'csrf',
- method: 'method',
- ...props,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it('renders modal with form included', () => {
- createComponent();
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('closing modal with ok button triggers form submit', () => {
- createComponent();
- const form = wrapper.find('form');
- jest.spyOn(form.element, 'submit').mockReturnValue();
- wrapper.find(GlModal).vm.$emit('ok');
- return wrapper.vm.$nextTick().then(() => {
- expect(form.element.submit).toHaveBeenCalled();
- expect(form.element.action).toContain(wrapper.props('url'));
- expect(new FormData(form.element).get('authenticity_token')).toEqual(
- wrapper.props('csrfToken'),
- );
- });
- });
-});
diff --git a/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js b/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js
index 67ace608127..695d1b686a5 100644
--- a/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js
+++ b/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js
@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import BitbucketServerStatusTable from '~/pages/import/bitbucket_server/status/components/bitbucket_server_status_table.vue';
-import BitbucketStatusTable from '~/import_projects/components/bitbucket_status_table.vue';
+import BitbucketStatusTable from '~/import_entities/import_projects/components/bitbucket_status_table.vue';
const BitbucketStatusTableStub = {
name: 'BitbucketStatusTable',
diff --git a/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap b/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap
index 8ccad7d5c22..324c9788309 100644
--- a/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap
+++ b/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap
@@ -10,7 +10,7 @@ exports[`Code Coverage when fetching data is successful matches the snapshot 1`]
<!---->
<gl-dropdown-stub
- category="tertiary"
+ category="primary"
headertext=""
size="medium"
text="rspec"
@@ -20,6 +20,7 @@ exports[`Code Coverage when fetching data is successful matches the snapshot 1`]
avatarurl=""
iconcolor=""
iconname=""
+ iconrightarialabel=""
iconrightname=""
ischecked="true"
ischeckitem="true"
@@ -34,6 +35,7 @@ exports[`Code Coverage when fetching data is successful matches the snapshot 1`]
avatarurl=""
iconcolor=""
iconname=""
+ iconrightarialabel=""
iconrightname=""
ischeckitem="true"
secondarytext=""
@@ -47,6 +49,7 @@ exports[`Code Coverage when fetching data is successful matches the snapshot 1`]
avatarurl=""
iconcolor=""
iconname=""
+ iconrightarialabel=""
iconrightname=""
ischeckitem="true"
secondarytext=""
diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
index e760cead760..0b58260ed1c 100644
--- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
+++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
@@ -21,11 +21,13 @@ const defaultProps = {
wikiAccessLevel: 20,
snippetsAccessLevel: 20,
pagesAccessLevel: 10,
+ analyticsAccessLevel: 20,
containerRegistryEnabled: true,
lfsEnabled: true,
emailsDisabled: false,
packagesEnabled: true,
showDefaultAwardEmojis: true,
+ allowEditingCommitMessages: false,
},
canDisableEmails: true,
canChangeVisibilityLevel: true,
@@ -49,7 +51,7 @@ describe('Settings Panel', () => {
let wrapper;
const mountComponent = (
- { currentSettings = {}, ...customProps } = {},
+ { currentSettings = {}, glFeatures = {}, ...customProps } = {},
mountFn = shallowMount,
) => {
const propsData = {
@@ -60,6 +62,9 @@ describe('Settings Panel', () => {
return mountFn(settingsPanel, {
propsData,
+ provide: {
+ glFeatures,
+ },
});
};
@@ -75,6 +80,8 @@ describe('Settings Panel', () => {
const findRepositoryFeatureSetting = () =>
findRepositoryFeatureProjectRow().find(projectFeatureSetting);
+ const findAnalyticsRow = () => wrapper.find({ ref: 'analytics-settings' });
+
beforeEach(() => {
wrapper = mountComponent();
});
@@ -539,4 +546,26 @@ describe('Settings Panel', () => {
expect(metricsSettingsRow.find('select').attributes('disabled')).toBe('disabled');
});
});
+
+ describe('Settings panel with feature flags', () => {
+ describe('Allow edit of commit message', () => {
+ it('should show the allow editing of commit messages checkbox', async () => {
+ wrapper = mountComponent({
+ glFeatures: { allowEditingCommitMessages: true },
+ });
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.find({ ref: 'allow-editing-commit-messages' }).exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('Analytics', () => {
+ it('should show the analytics toggle', async () => {
+ await wrapper.vm.$nextTick();
+
+ expect(findAnalyticsRow().exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js
new file mode 100644
index 00000000000..ae2a9e5065d
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js
@@ -0,0 +1,116 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import { GlFormInput, GlFormTextarea } from '@gitlab/ui';
+
+import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
+
+import { mockCommitMessage, mockDefaultBranch } from '../../mock_data';
+
+describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
+ let wrapper;
+
+ const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => {
+ wrapper = mountFn(CommitForm, {
+ propsData: {
+ defaultMessage: mockCommitMessage,
+ defaultBranch: mockDefaultBranch,
+ ...props,
+ },
+
+ // attachToDocument is required for input/submit events
+ attachToDocument: mountFn === mount,
+ });
+ };
+
+ const findCommitTextarea = () => wrapper.find(GlFormTextarea);
+ const findBranchInput = () => wrapper.find(GlFormInput);
+ const findNewMrCheckbox = () => wrapper.find('[data-testid="new-mr-checkbox"]');
+ const findSubmitBtn = () => wrapper.find('[type="submit"]');
+ const findCancelBtn = () => wrapper.find('[type="reset"]');
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when the form is displayed', () => {
+ beforeEach(async () => {
+ createComponent();
+ });
+
+ it('shows a default commit message', () => {
+ expect(findCommitTextarea().attributes('value')).toBe(mockCommitMessage);
+ });
+
+ it('shows a default branch', () => {
+ expect(findBranchInput().attributes('value')).toBe(mockDefaultBranch);
+ });
+
+ it('shows buttons', () => {
+ expect(findSubmitBtn().exists()).toBe(true);
+ expect(findCancelBtn().exists()).toBe(true);
+ });
+
+ it('does not show a new MR checkbox by default', () => {
+ expect(findNewMrCheckbox().exists()).toBe(false);
+ });
+ });
+
+ describe('when buttons are clicked', () => {
+ beforeEach(async () => {
+ createComponent({}, mount);
+ });
+
+ it('emits an event when the form submits', () => {
+ findSubmitBtn().trigger('click');
+
+ expect(wrapper.emitted('submit')[0]).toEqual([
+ {
+ message: mockCommitMessage,
+ branch: mockDefaultBranch,
+ openMergeRequest: false,
+ },
+ ]);
+ });
+
+ it('emits an event when the form resets', () => {
+ findCancelBtn().trigger('click');
+
+ expect(wrapper.emitted('cancel')).toHaveLength(1);
+ });
+ });
+
+ describe('when user inputs values', () => {
+ const anotherMessage = 'Another commit message';
+ const anotherBranch = 'my-branch';
+
+ beforeEach(() => {
+ createComponent({}, mount);
+
+ findCommitTextarea().setValue(anotherMessage);
+ findBranchInput().setValue(anotherBranch);
+ });
+
+ it('shows a new MR checkbox', () => {
+ expect(findNewMrCheckbox().exists()).toBe(true);
+ });
+
+ it('emits an event with values', async () => {
+ await findNewMrCheckbox().setChecked();
+ await findSubmitBtn().trigger('click');
+
+ expect(wrapper.emitted('submit')[0]).toEqual([
+ {
+ message: anotherMessage,
+ branch: anotherBranch,
+ openMergeRequest: true,
+ },
+ ]);
+ });
+
+ it('when the commit message is empty, submit button is disabled', async () => {
+ await findCommitTextarea().setValue('');
+
+ expect(findSubmitBtn().attributes('disabled')).toBe('disabled');
+ });
+ });
+});
diff --git a/spec/frontend/ci_lint/components/ci_lint_results_spec.js b/spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js
index 93c2d2dbcf3..e9c6ed60860 100644
--- a/spec/frontend/ci_lint/components/ci_lint_results_spec.js
+++ b/spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js
@@ -1,8 +1,8 @@
import { shallowMount, mount } from '@vue/test-utils';
import { GlTable, GlLink } from '@gitlab/ui';
-import CiLintResults from '~/ci_lint/components/ci_lint_results.vue';
+import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
-import { mockJobs, mockErrors, mockWarnings } from '../mock_data';
+import { mockJobs, mockErrors, mockWarnings } from '../../mock_data';
describe('CI Lint Results', () => {
let wrapper;
diff --git a/spec/frontend/ci_lint/components/ci_lint_warnings_spec.js b/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js
index 6e0a4881e14..b441d26c146 100644
--- a/spec/frontend/ci_lint/components/ci_lint_warnings_spec.js
+++ b/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper';
-import CiLintWarnings from '~/ci_lint/components/ci_lint_warnings.vue';
+import CiLintWarnings from '~/pipeline_editor/components/lint/ci_lint_warnings.vue';
const warnings = ['warning 1', 'warning 2', 'warning 3'];
diff --git a/spec/frontend/pipeline_editor/components/text_editor_spec.js b/spec/frontend/pipeline_editor/components/text_editor_spec.js
index 39d205839f4..18f71ebc95c 100644
--- a/spec/frontend/pipeline_editor/components/text_editor_spec.js
+++ b/spec/frontend/pipeline_editor/components/text_editor_spec.js
@@ -6,12 +6,16 @@ import TextEditor from '~/pipeline_editor/components/text_editor.vue';
describe('~/pipeline_editor/components/text_editor.vue', () => {
let wrapper;
+ const editorReadyListener = jest.fn();
- const createComponent = (props = {}, mountFn = shallowMount) => {
+ const createComponent = (attrs = {}, mountFn = shallowMount) => {
wrapper = mountFn(TextEditor, {
- propsData: {
+ attrs: {
value: mockCiYml,
- ...props,
+ ...attrs,
+ },
+ listeners: {
+ 'editor-ready': editorReadyListener,
},
});
};
@@ -28,14 +32,13 @@ describe('~/pipeline_editor/components/text_editor.vue', () => {
expect(findEditor().props('value')).toBe(mockCiYml);
});
- it('editor is readony and configured for .yml', () => {
- expect(findEditor().props('editorOptions')).toEqual({ readOnly: true });
+ it('editor is configured for .yml', () => {
expect(findEditor().props('fileName')).toBe('*.yml');
});
- it('bubbles up editor-ready event', () => {
+ it('bubbles up events', () => {
findEditor().vm.$emit('editor-ready');
- expect(wrapper.emitted('editor-ready')).toHaveLength(1);
+ expect(editorReadyListener).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/ci_lint/graphql/__snapshots__/resolvers_spec.js.snap b/spec/frontend/pipeline_editor/graphql/__snapshots__/resolvers_spec.js.snap
index 87bec82e350..d7d4d0af90c 100644
--- a/spec/frontend/ci_lint/graphql/__snapshots__/resolvers_spec.js.snap
+++ b/spec/frontend/pipeline_editor/graphql/__snapshots__/resolvers_spec.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`~/ci_lint/graphql/resolvers Mutation lintCI resolves lint data with type names 1`] = `
+exports[`~/pipeline_editor/graphql/resolvers Mutation lintCI lint data is as expected 1`] = `
Object {
"__typename": "CiLintContent",
"errors": Array [],
diff --git a/spec/frontend/pipeline_editor/graphql/resolvers_spec.js b/spec/frontend/pipeline_editor/graphql/resolvers_spec.js
index 90acdf3ec0b..b531f8af797 100644
--- a/spec/frontend/pipeline_editor/graphql/resolvers_spec.js
+++ b/spec/frontend/pipeline_editor/graphql/resolvers_spec.js
@@ -1,6 +1,14 @@
+import MockAdapter from 'axios-mock-adapter';
import Api from '~/api';
-import { mockProjectPath, mockDefaultBranch, mockCiConfigPath, mockCiYml } from '../mock_data';
-
+import {
+ mockCiConfigPath,
+ mockCiYml,
+ mockDefaultBranch,
+ mockLintResponse,
+ mockProjectPath,
+} from '../mock_data';
+import httpStatus from '~/lib/utils/http_status';
+import axios from '~/lib/utils/axios_utils';
import { resolvers } from '~/pipeline_editor/graphql/resolvers';
jest.mock('~/api', () => {
@@ -39,4 +47,43 @@ describe('~/pipeline_editor/graphql/resolvers', () => {
});
});
});
+
+ describe('Mutation', () => {
+ describe('lintCI', () => {
+ let mock;
+ let result;
+
+ const endpoint = '/ci/lint';
+
+ beforeEach(async () => {
+ mock = new MockAdapter(axios);
+ mock.onPost(endpoint).reply(httpStatus.OK, mockLintResponse);
+
+ result = await resolvers.Mutation.lintCI(null, {
+ endpoint,
+ content: 'content',
+ dry_run: true,
+ });
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ /* eslint-disable no-underscore-dangle */
+ it('lint data has correct type names', async () => {
+ expect(result.__typename).toBe('CiLintContent');
+
+ expect(result.jobs[0].__typename).toBe('CiLintJob');
+ expect(result.jobs[1].__typename).toBe('CiLintJob');
+
+ expect(result.jobs[1].only.__typename).toBe('CiLintJobOnlyPolicy');
+ });
+ /* eslint-enable no-underscore-dangle */
+
+ it('lint data is as expected', () => {
+ expect(result).toMatchSnapshot();
+ });
+ });
+ });
});
diff --git a/spec/frontend/pipeline_editor/mock_data.js b/spec/frontend/pipeline_editor/mock_data.js
index 96fa6e5e004..d882490c272 100644
--- a/spec/frontend/pipeline_editor/mock_data.js
+++ b/spec/frontend/pipeline_editor/mock_data.js
@@ -1,5 +1,8 @@
export const mockProjectPath = 'user1/project1';
export const mockDefaultBranch = 'master';
+export const mockNewMergeRequestPath = '/-/merge_requests/new';
+export const mockCommitId = 'aabbccdd';
+export const mockCommitMessage = 'My commit message';
export const mockCiConfigPath = '.gitlab-ci.yml';
export const mockCiYml = `
@@ -8,3 +11,97 @@ job1:
script:
- echo 'test'
`;
+
+export const mockCiConfigQueryResponse = {
+ data: {
+ ciConfig: {
+ errors: [],
+ stages: [],
+ status: '',
+ },
+ },
+};
+
+export const mockLintResponse = {
+ valid: true,
+ errors: [],
+ warnings: [],
+ jobs: [
+ {
+ name: 'job_1',
+ stage: 'test',
+ before_script: ["echo 'before script 1'"],
+ script: ["echo 'script 1'"],
+ after_script: ["echo 'after script 1"],
+ tag_list: ['tag 1'],
+ environment: 'prd',
+ when: 'on_success',
+ allow_failure: false,
+ only: null,
+ except: { refs: ['master@gitlab-org/gitlab', '/^release/.*$/@gitlab-org/gitlab'] },
+ },
+ {
+ name: 'job_2',
+ stage: 'test',
+ before_script: ["echo 'before script 2'"],
+ script: ["echo 'script 2'"],
+ after_script: ["echo 'after script 2"],
+ tag_list: ['tag 2'],
+ environment: 'stg',
+ when: 'on_success',
+ allow_failure: true,
+ only: { refs: ['web', 'chat', 'pushes'] },
+ except: { refs: ['master@gitlab-org/gitlab', '/^release/.*$/@gitlab-org/gitlab'] },
+ },
+ ],
+};
+
+export const mockJobs = [
+ {
+ name: 'job_1',
+ stage: 'build',
+ beforeScript: [],
+ script: ["echo 'Building'"],
+ afterScript: [],
+ tagList: [],
+ environment: null,
+ when: 'on_success',
+ allowFailure: true,
+ only: { refs: ['web', 'chat', 'pushes'] },
+ except: null,
+ },
+ {
+ name: 'multi_project_job',
+ stage: 'test',
+ beforeScript: [],
+ script: [],
+ afterScript: [],
+ tagList: [],
+ environment: null,
+ when: 'on_success',
+ allowFailure: false,
+ only: { refs: ['branches', 'tags'] },
+ except: null,
+ },
+ {
+ name: 'job_2',
+ stage: 'test',
+ beforeScript: ["echo 'before script'"],
+ script: ["echo 'script'"],
+ afterScript: ["echo 'after script"],
+ tagList: [],
+ environment: null,
+ when: 'on_success',
+ allowFailure: false,
+ only: { refs: ['branches@gitlab-org/gitlab'] },
+ except: { refs: ['master@gitlab-org/gitlab', '/^release/.*$/@gitlab-org/gitlab'] },
+ },
+];
+
+export const mockErrors = [
+ '"job_1 job: chosen stage does not exist; available stages are .pre, build, test, deploy, .post"',
+];
+
+export const mockWarnings = [
+ '"jobs:multi_project_job may allow multiple pipelines to run for a single action due to `rules:when` clause with no `workflow:rules` - read more: https://docs.gitlab.com/ee/ci/troubleshooting.html#pipeline-warnings"',
+];
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
index 46523baadf3..14d6b03645c 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
@@ -1,139 +1,445 @@
import { nextTick } from 'vue';
-import { shallowMount } from '@vue/test-utils';
-import { GlAlert, GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui';
+import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
+import {
+ GlAlert,
+ GlButton,
+ GlFormInput,
+ GlFormTextarea,
+ GlLoadingIcon,
+ GlTabs,
+ GlTab,
+} from '@gitlab/ui';
+import waitForPromises from 'helpers/wait_for_promises';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
-import { mockProjectPath, mockDefaultBranch, mockCiConfigPath, mockCiYml } from './mock_data';
-import TextEditor from '~/pipeline_editor/components/text_editor.vue';
-import EditorLite from '~/vue_shared/components/editor_lite.vue';
+import { objectToQuery, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility';
+import {
+ mockCiConfigPath,
+ mockCiConfigQueryResponse,
+ mockCiYml,
+ mockCommitId,
+ mockCommitMessage,
+ mockDefaultBranch,
+ mockProjectPath,
+ mockNewMergeRequestPath,
+} from './mock_data';
+
+import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
+import getCiConfig from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
+import TextEditor from '~/pipeline_editor/components/text_editor.vue';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ redirectTo: jest.fn(),
+ refreshCurrentPage: jest.fn(),
+ objectToQuery: jest.requireActual('~/lib/utils/url_utility').objectToQuery,
+ mergeUrlParams: jest.requireActual('~/lib/utils/url_utility').mergeUrlParams,
+}));
describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
let wrapper;
- const createComponent = (
- { props = {}, data = {}, loading = false } = {},
+ let mockApollo;
+ let mockBlobContentData;
+ let mockCiConfigData;
+ let mockMutate;
+
+ const createComponent = ({
+ props = {},
+ blobLoading = false,
+ lintLoading = false,
+ options = {},
mountFn = shallowMount,
- ) => {
+ provide = {
+ glFeatures: {
+ ciConfigVisualizationTab: true,
+ },
+ },
+ } = {}) => {
+ mockMutate = jest.fn().mockResolvedValue({
+ data: {
+ commitCreate: {
+ errors: [],
+ commit: {},
+ },
+ },
+ });
+
wrapper = mountFn(PipelineEditorApp, {
propsData: {
- projectPath: mockProjectPath,
- defaultBranch: mockDefaultBranch,
ciConfigPath: mockCiConfigPath,
+ commitId: mockCommitId,
+ defaultBranch: mockDefaultBranch,
+ projectPath: mockProjectPath,
+ newMergeRequestPath: mockNewMergeRequestPath,
...props,
},
- data() {
- return data;
- },
+ provide,
stubs: {
GlTabs,
+ GlButton,
+ CommitForm,
+ EditorLite: {
+ template: '<div/>',
+ },
TextEditor,
},
mocks: {
$apollo: {
queries: {
content: {
- loading,
+ loading: blobLoading,
+ },
+ ciConfigData: {
+ loading: lintLoading,
},
},
+ mutate: mockMutate,
},
},
+ // attachToDocument is required for input/submit events
+ attachToDocument: mountFn === mount,
+ ...options,
});
};
+ const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
+ const handlers = [[getCiConfig, mockCiConfigData]];
+ const resolvers = {
+ Query: {
+ blobContent() {
+ return {
+ __typename: 'BlobContent',
+ rawData: mockBlobContentData(),
+ };
+ },
+ },
+ };
+
+ mockApollo = createMockApollo(handlers, resolvers);
+
+ const options = {
+ localVue,
+ mocks: {},
+ apolloProvider: mockApollo,
+ };
+
+ createComponent({ props, options }, mountFn);
+ };
+
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findAlert = () => wrapper.find(GlAlert);
+ const findBlobFailureAlert = () => wrapper.find(GlAlert);
const findTabAt = i => wrapper.findAll(GlTab).at(i);
- const findEditorLite = () => wrapper.find(EditorLite);
+ const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]');
+ const findTextEditor = () => wrapper.find(TextEditor);
+ const findCommitForm = () => wrapper.find(CommitForm);
+ const findPipelineGraph = () => wrapper.find(PipelineGraph);
+ const findCommitBtnLoadingIcon = () => wrapper.find('[type="submit"]').find(GlLoadingIcon);
beforeEach(() => {
- createComponent();
+ mockBlobContentData = jest.fn();
+ mockCiConfigData = jest.fn().mockResolvedValue(mockCiConfigQueryResponse);
});
afterEach(() => {
+ mockBlobContentData.mockReset();
+ mockCiConfigData.mockReset();
+ refreshCurrentPage.mockReset();
+ redirectTo.mockReset();
+ mockMutate.mockReset();
+
wrapper.destroy();
wrapper = null;
});
- it('displays content', () => {
- createComponent({ data: { content: mockCiYml } });
-
- expect(findLoadingIcon().exists()).toBe(false);
- expect(findEditorLite().props('value')).toBe(mockCiYml);
- });
-
- it('displays a loading icon if the query is loading', () => {
- createComponent({ loading: true });
+ it('displays a loading icon if the blob query is loading', () => {
+ createComponent({ blobLoading: true });
expect(findLoadingIcon().exists()).toBe(true);
+ expect(findTextEditor().exists()).toBe(false);
});
describe('tabs', () => {
- it('displays tabs and their content', () => {
- createComponent({ data: { content: mockCiYml } });
-
- expect(
- findTabAt(0)
- .find(EditorLite)
- .exists(),
- ).toBe(true);
- expect(
- findTabAt(1)
- .find(PipelineGraph)
- .exists(),
- ).toBe(true);
+ describe('editor tab', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('displays the tab and its content', async () => {
+ expect(
+ findTabAt(0)
+ .find(TextEditor)
+ .exists(),
+ ).toBe(true);
+ });
+
+ it('displays tab lazily, until editor is ready', async () => {
+ expect(findTabAt(0).attributes('lazy')).toBe('true');
+
+ findTextEditor().vm.$emit('editor-ready');
+
+ await nextTick();
+
+ expect(findTabAt(0).attributes('lazy')).toBe(undefined);
+ });
});
- it('displays editor tab lazily, until editor is ready', async () => {
- createComponent({ data: { content: mockCiYml } });
+ describe('visualization tab', () => {
+ describe('with feature flag on', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('display the tab', () => {
+ expect(findVisualizationTab().exists()).toBe(true);
+ });
+
+ it('displays a loading icon if the lint query is loading', () => {
+ createComponent({ lintLoading: true });
- expect(findTabAt(0).attributes('lazy')).toBe('true');
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
+ });
- findEditorLite().vm.$emit('editor-ready');
- await nextTick();
+ describe('with feature flag off', () => {
+ beforeEach(() => {
+ createComponent({ provide: { glFeatures: { ciConfigVisualizationTab: false } } });
+ });
- expect(findTabAt(0).attributes('lazy')).toBe(undefined);
+ it('does not display the tab', () => {
+ expect(findVisualizationTab().exists()).toBe(false);
+ });
+ });
});
});
- describe('when in error state', () => {
- class MockError extends Error {
- constructor(message, data) {
- super(message);
- if (data) {
- this.networkError = {
- response: { data },
- };
+ describe('when data is set', () => {
+ beforeEach(async () => {
+ createComponent({ mountFn: mount });
+
+ await wrapper.setData({
+ content: mockCiYml,
+ contentModel: mockCiYml,
+ });
+ });
+
+ it('displays content after the query loads', () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ expect(findTextEditor().attributes('value')).toBe(mockCiYml);
+ });
+
+ describe('commit form', () => {
+ const mockVariables = {
+ content: mockCiYml,
+ filePath: mockCiConfigPath,
+ lastCommitId: mockCommitId,
+ message: mockCommitMessage,
+ projectPath: mockProjectPath,
+ startBranch: mockDefaultBranch,
+ };
+
+ const findInForm = selector => findCommitForm().find(selector);
+
+ const submitCommit = async ({
+ message = mockCommitMessage,
+ branch = mockDefaultBranch,
+ openMergeRequest = false,
+ } = {}) => {
+ await findInForm(GlFormTextarea).setValue(message);
+ await findInForm(GlFormInput).setValue(branch);
+ if (openMergeRequest) {
+ await findInForm('[data-testid="new-mr-checkbox"]').setChecked(openMergeRequest);
}
- }
- }
+ await findInForm('[type="submit"]').trigger('click');
+ };
+
+ const cancelCommitForm = async () => {
+ const findCancelBtn = () => wrapper.find('[type="reset"]');
+ await findCancelBtn().trigger('click');
+ };
+
+ describe('when the user commits changes to the current branch', () => {
+ beforeEach(async () => {
+ await submitCommit();
+ });
+
+ it('calls the mutation with the default branch', () => {
+ expect(mockMutate).toHaveBeenCalledWith({
+ mutation: expect.any(Object),
+ variables: {
+ ...mockVariables,
+ branch: mockDefaultBranch,
+ },
+ });
+ });
+
+ it('refreshes the page', () => {
+ expect(refreshCurrentPage).toHaveBeenCalled();
+ });
+
+ it('shows no saving state', () => {
+ expect(findCommitBtnLoadingIcon().exists()).toBe(false);
+ });
+ });
+
+ describe('when the user commits changes to a new branch', () => {
+ const newBranch = 'new-branch';
+
+ beforeEach(async () => {
+ await submitCommit({
+ branch: newBranch,
+ });
+ });
+
+ it('calls the mutation with the new branch', () => {
+ expect(mockMutate).toHaveBeenCalledWith({
+ mutation: expect.any(Object),
+ variables: {
+ ...mockVariables,
+ branch: newBranch,
+ },
+ });
+ });
+
+ it('refreshes the page', () => {
+ expect(refreshCurrentPage).toHaveBeenCalledWith();
+ });
+ });
+
+ describe('when the user commits changes to open a new merge request', () => {
+ const newBranch = 'new-branch';
+
+ beforeEach(async () => {
+ await submitCommit({
+ branch: newBranch,
+ openMergeRequest: true,
+ });
+ });
+
+ it('redirects to the merge request page with source and target branches', () => {
+ const branchesQuery = objectToQuery({
+ 'merge_request[source_branch]': newBranch,
+ 'merge_request[target_branch]': mockDefaultBranch,
+ });
+
+ expect(redirectTo).toHaveBeenCalledWith(`${mockNewMergeRequestPath}?${branchesQuery}`);
+ });
+ });
+
+ describe('when the commit is ocurring', () => {
+ it('shows a saving state', async () => {
+ await mockMutate.mockImplementationOnce(() => {
+ expect(findCommitBtnLoadingIcon().exists()).toBe(true);
+ return Promise.resolve();
+ });
+
+ await submitCommit({
+ message: mockCommitMessage,
+ branch: mockDefaultBranch,
+ openMergeRequest: false,
+ });
+ });
+ });
+
+ describe('when the commit fails', () => {
+ it('shows a the error message', async () => {
+ mockMutate.mockRejectedValueOnce(new Error('commit failed'));
- it('shows a generic error', () => {
- const error = new MockError('An error message');
- createComponent({ data: { error } });
+ await submitCommit();
- expect(findAlert().text()).toBe('CI file could not be loaded: An error message');
+ await waitForPromises();
+
+ expect(findAlert().text()).toMatchInterpolatedText(
+ 'The GitLab CI configuration could not be updated. commit failed',
+ );
+ });
+
+ it('shows an unkown error', async () => {
+ mockMutate.mockRejectedValueOnce();
+
+ await submitCommit();
+
+ await waitForPromises();
+
+ expect(findAlert().text()).toMatchInterpolatedText(
+ 'The GitLab CI configuration could not be updated.',
+ );
+ });
+ });
+
+ describe('when the commit form is cancelled', () => {
+ const otherContent = 'other content';
+
+ beforeEach(async () => {
+ findTextEditor().vm.$emit('input', otherContent);
+ await nextTick();
+ });
+
+ it('content is restored after cancel is called', async () => {
+ await cancelCommitForm();
+
+ expect(findTextEditor().attributes('value')).toBe(mockCiYml);
+ });
+ });
+ });
+ });
+
+ describe('displays fetch content errors', () => {
+ it('no error is shown when data is set', async () => {
+ mockBlobContentData.mockResolvedValue(mockCiYml);
+ createComponentWithApollo();
+
+ await waitForPromises();
+
+ expect(findBlobFailureAlert().exists()).toBe(false);
+ expect(findTextEditor().attributes('value')).toBe(mockCiYml);
});
- it('shows a ref missing error state', () => {
- const error = new MockError('Ref missing!', {
- error: 'ref is missing, ref is empty',
+ it('shows a 404 error message', async () => {
+ mockBlobContentData.mockRejectedValueOnce({
+ response: {
+ status: 404,
+ },
});
- createComponent({ data: { error } });
+ createComponentWithApollo();
+
+ await waitForPromises();
- expect(findAlert().text()).toMatch(
- 'CI file could not be loaded: ref is missing, ref is empty',
+ expect(findBlobFailureAlert().text()).toBe(
+ 'No CI file found in this repository, please add one.',
);
});
- it('shows a file missing error state', async () => {
- const error = new MockError('File missing!', {
- message: 'file not found',
+ it('shows a 400 error message', async () => {
+ mockBlobContentData.mockRejectedValueOnce({
+ response: {
+ status: 400,
+ },
});
+ createComponentWithApollo();
+
+ await waitForPromises();
+
+ expect(findBlobFailureAlert().text()).toBe(
+ 'Repository does not have a default branch, please set one.',
+ );
+ });
- await wrapper.setData({ error });
+ it('shows a unkown error message', async () => {
+ mockBlobContentData.mockRejectedValueOnce(new Error('My error!'));
+ createComponentWithApollo();
+ await waitForPromises();
- expect(findAlert().text()).toMatch('CI file could not be loaded: file not found');
+ expect(findBlobFailureAlert().text()).toBe(
+ 'The CI configuration was not loaded, please try again.',
+ );
});
});
});
diff --git a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
index 197f646a22e..b42339f626e 100644
--- a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
+++ b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
@@ -5,7 +5,14 @@ import waitForPromises from 'helpers/wait_for_promises';
import httpStatusCodes from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
import PipelineNewForm from '~/pipeline_new/components/pipeline_new_form.vue';
-import { mockRefs, mockParams, mockPostParams, mockProjectId, mockError } from '../mock_data';
+import {
+ mockBranches,
+ mockTags,
+ mockParams,
+ mockPostParams,
+ mockProjectId,
+ mockError,
+} from '../mock_data';
import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({
@@ -37,6 +44,10 @@ describe('Pipeline New Form', () => {
const findWarnings = () => wrapper.findAll('[data-testid="run-pipeline-warning"]');
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const getExpectedPostParams = () => JSON.parse(mock.history.post[0].data);
+ const changeRef = i =>
+ findDropdownItems()
+ .at(i)
+ .vm.$emit('click');
const createComponent = (term = '', props = {}, method = shallowMount) => {
wrapper = method(PipelineNewForm, {
@@ -44,7 +55,8 @@ describe('Pipeline New Form', () => {
projectId: mockProjectId,
pipelinesPath,
configVariablesPath,
- refs: mockRefs,
+ branches: mockBranches,
+ tags: mockTags,
defaultBranch: 'master',
settingsLink: '',
maxWarnings: 25,
@@ -76,8 +88,11 @@ describe('Pipeline New Form', () => {
});
it('displays dropdown with all branches and tags', () => {
+ const refLength = mockBranches.length + mockTags.length;
+
createComponent();
- expect(findDropdownItems()).toHaveLength(mockRefs.length);
+
+ expect(findDropdownItems()).toHaveLength(refLength);
});
it('when user enters search term the list is filtered', () => {
@@ -130,15 +145,6 @@ describe('Pipeline New Form', () => {
expect(findVariableRows()).toHaveLength(2);
});
- it('creates a pipeline on submit', async () => {
- findForm().vm.$emit('submit', dummySubmitEvent);
-
- await waitForPromises();
-
- expect(getExpectedPostParams()).toEqual(mockPostParams);
- expect(redirectTo).toHaveBeenCalledWith(`${pipelinesPath}/${postResponse.id}`);
- });
-
it('creates blank variable on input change event', async () => {
const input = findKeyInputs().at(2);
input.element.value = 'test_var_2';
@@ -150,45 +156,81 @@ describe('Pipeline New Form', () => {
expect(findKeyInputs().at(3).element.value).toBe('');
expect(findValueInputs().at(3).element.value).toBe('');
});
+ });
- describe('when the form has been modified', () => {
- const selectRef = i =>
- findDropdownItems()
- .at(i)
- .vm.$emit('click');
+ describe('Pipeline creation', () => {
+ beforeEach(async () => {
+ mock.onPost(pipelinesPath).reply(httpStatusCodes.OK, postResponse);
- beforeEach(async () => {
- const input = findKeyInputs().at(0);
- input.element.value = 'test_var_2';
- input.trigger('change');
+ await waitForPromises();
+ });
+ it('creates pipeline with full ref and variables', async () => {
+ createComponent();
- findRemoveIcons()
- .at(1)
- .trigger('click');
+ changeRef(0);
- await wrapper.vm.$nextTick();
- });
+ findForm().vm.$emit('submit', dummySubmitEvent);
- it('form values are restored when the ref changes', async () => {
- expect(findVariableRows()).toHaveLength(2);
+ await waitForPromises();
- selectRef(1);
- await waitForPromises();
+ expect(getExpectedPostParams().ref).toEqual(wrapper.vm.$data.refValue.fullName);
+ expect(redirectTo).toHaveBeenCalledWith(`${pipelinesPath}/${postResponse.id}`);
+ });
+ it('creates a pipeline with short ref and variables', async () => {
+ // query params are used
+ createComponent('', mockParams);
- expect(findVariableRows()).toHaveLength(3);
- expect(findKeyInputs().at(0).element.value).toBe('test_var');
- });
+ await waitForPromises();
- it('form values are restored again when the ref is reverted', async () => {
- selectRef(1);
- await waitForPromises();
+ findForm().vm.$emit('submit', dummySubmitEvent);
- selectRef(2);
- await waitForPromises();
+ await waitForPromises();
- expect(findVariableRows()).toHaveLength(2);
- expect(findKeyInputs().at(0).element.value).toBe('test_var_2');
- });
+ expect(getExpectedPostParams()).toEqual(mockPostParams);
+ expect(redirectTo).toHaveBeenCalledWith(`${pipelinesPath}/${postResponse.id}`);
+ });
+ });
+
+ describe('When the ref has been changed', () => {
+ beforeEach(async () => {
+ createComponent('', {}, mount);
+
+ await waitForPromises();
+ });
+ it('variables persist between ref changes', async () => {
+ changeRef(0); // change to master
+
+ await waitForPromises();
+
+ const masterInput = findKeyInputs().at(0);
+ masterInput.element.value = 'build_var';
+ masterInput.trigger('change');
+
+ await wrapper.vm.$nextTick();
+
+ changeRef(1); // change to branch-1
+
+ await waitForPromises();
+
+ const branchOneInput = findKeyInputs().at(0);
+ branchOneInput.element.value = 'deploy_var';
+ branchOneInput.trigger('change');
+
+ await wrapper.vm.$nextTick();
+
+ changeRef(0); // change back to master
+
+ await waitForPromises();
+
+ expect(findKeyInputs().at(0).element.value).toBe('build_var');
+ expect(findVariableRows().length).toBe(2);
+
+ changeRef(1); // change back to branch-1
+
+ await waitForPromises();
+
+ expect(findKeyInputs().at(0).element.value).toBe('deploy_var');
+ expect(findVariableRows().length).toBe(2);
});
});
@@ -321,6 +363,7 @@ describe('Pipeline New Form', () => {
it('shows the correct warning title', () => {
const { length } = mockError.warnings;
+
expect(findWarningAlertSummary().attributes('message')).toBe(`${length} warnings found:`);
});
diff --git a/spec/frontend/pipeline_new/mock_data.js b/spec/frontend/pipeline_new/mock_data.js
index cdbd6d4437e..feb24ec602d 100644
--- a/spec/frontend/pipeline_new/mock_data.js
+++ b/spec/frontend/pipeline_new/mock_data.js
@@ -1,4 +1,14 @@
-export const mockRefs = ['master', 'branch-1', 'tag-1'];
+export const mockBranches = [
+ { shortName: 'master', fullName: 'refs/heads/master' },
+ { shortName: 'branch-1', fullName: 'refs/heads/branch-1' },
+ { shortName: 'branch-2', fullName: 'refs/heads/branch-2' },
+];
+
+export const mockTags = [
+ { shortName: '1.0.0', fullName: 'refs/tags/1.0.0' },
+ { shortName: '1.1.0', fullName: 'refs/tags/1.1.0' },
+ { shortName: '1.2.0', fullName: 'refs/tags/1.2.0' },
+];
export const mockParams = {
refParam: 'tag-1',
@@ -31,3 +41,7 @@ export const mockError = {
],
total_warnings: 7,
};
+
+export const mockBranchRefs = ['master', 'dev', 'release'];
+
+export const mockTagRefs = ['1.0.0', '1.1.0', '1.2.0'];
diff --git a/spec/frontend/pipeline_new/utils/format_refs_spec.js b/spec/frontend/pipeline_new/utils/format_refs_spec.js
new file mode 100644
index 00000000000..1fda6a8af83
--- /dev/null
+++ b/spec/frontend/pipeline_new/utils/format_refs_spec.js
@@ -0,0 +1,21 @@
+import formatRefs from '~/pipeline_new/utils/format_refs';
+import { BRANCH_REF_TYPE, TAG_REF_TYPE } from '~/pipeline_new/constants';
+import { mockBranchRefs, mockTagRefs } from '../mock_data';
+
+describe('Format refs util', () => {
+ it('formats branch ref correctly', () => {
+ expect(formatRefs(mockBranchRefs, BRANCH_REF_TYPE)).toEqual([
+ { fullName: 'refs/heads/master', shortName: 'master' },
+ { fullName: 'refs/heads/dev', shortName: 'dev' },
+ { fullName: 'refs/heads/release', shortName: 'release' },
+ ]);
+ });
+
+ it('formats tag ref correctly', () => {
+ expect(formatRefs(mockTagRefs, TAG_REF_TYPE)).toEqual([
+ { fullName: 'refs/tags/1.0.0', shortName: '1.0.0' },
+ { fullName: 'refs/tags/1.1.0', shortName: '1.1.0' },
+ { fullName: 'refs/tags/1.2.0', shortName: '1.2.0' },
+ ]);
+ });
+});
diff --git a/spec/frontend/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index 79356664834..28a73c8863c 100644
--- a/spec/frontend/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
@@ -1,58 +1,52 @@
-import Vue from 'vue';
-import emptyStateComp from '~/pipelines/components/pipelines_list/empty_state.vue';
-import mountComponent from '../helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
+import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
describe('Pipelines Empty State', () => {
- let component;
- let EmptyStateComponent;
+ let wrapper;
+
+ const findGetStartedButton = () => wrapper.find('[data-testid="get-started-pipelines"]');
+ const findInfoText = () => wrapper.find('[data-testid="info-text"]').text();
+ const createWrapper = () => {
+ wrapper = shallowMount(EmptyState, {
+ propsData: {
+ helpPagePath: 'foo',
+ emptyStateSvgPath: 'foo',
+ canSetCi: true,
+ },
+ });
+ };
- beforeEach(() => {
- EmptyStateComponent = Vue.extend(emptyStateComp);
+ describe('renders', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
- component = mountComponent(EmptyStateComponent, {
- helpPagePath: 'foo',
- emptyStateSvgPath: 'foo',
- canSetCi: true,
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
});
- });
- afterEach(() => {
- component.$destroy();
- });
+ it('should render empty state SVG', () => {
+ expect(wrapper.find('img').attributes('src')).toBe('foo');
+ });
- it('should render empty state SVG', () => {
- expect(component.$el.querySelector('.svg-content svg')).toBeDefined();
- });
+ it('should render empty state header', () => {
+ expect(wrapper.find('[data-testid="header-text"]').text()).toBe('Build with confidence');
+ });
- it('should render empty state information', () => {
- expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence');
-
- expect(
- component.$el
- .querySelector('p')
- .innerHTML.trim()
- .replace(/\n+\s+/m, ' ')
- .replace(/\s\s+/g, ' '),
- ).toContain('Continuous Integration can help catch bugs by running your tests automatically,');
-
- expect(
- component.$el
- .querySelector('p')
- .innerHTML.trim()
- .replace(/\n+\s+/m, ' ')
- .replace(/\s\s+/g, ' '),
- ).toContain(
- 'while Continuous Deployment can help you deliver code to your product environment',
- );
- });
+ it('should render a link with provided help path', () => {
+ expect(findGetStartedButton().attributes('href')).toBe('foo');
+ });
- it('should render a link with provided help path', () => {
- expect(component.$el.querySelector('.js-get-started-pipelines').getAttribute('href')).toEqual(
- 'foo',
- );
+ it('should render empty state information', () => {
+ expect(findInfoText()).toContain(
+ 'Continuous Integration can help catch bugs by running your tests automatically',
+ 'while Continuous Deployment can help you deliver code to your product environment',
+ );
+ });
- expect(component.$el.querySelector('.js-get-started-pipelines').textContent).toContain(
- 'Get started with Pipelines',
- );
+ it('should render a button', () => {
+ expect(findGetStartedButton().text()).toBe('Get started with Pipelines');
+ });
});
});
diff --git a/spec/frontend/pipelines/graph/graph_component_legacy_spec.js b/spec/frontend/pipelines/graph/graph_component_legacy_spec.js
new file mode 100644
index 00000000000..3b1909b6564
--- /dev/null
+++ b/spec/frontend/pipelines/graph/graph_component_legacy_spec.js
@@ -0,0 +1,306 @@
+import Vue from 'vue';
+import { mount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { setHTMLFixture } from 'helpers/fixtures';
+import PipelineStore from '~/pipelines/stores/pipeline_store';
+import GraphComponentLegacy from '~/pipelines/components/graph/graph_component_legacy.vue';
+import StageColumnComponentLegacy from '~/pipelines/components/graph/stage_column_component_legacy.vue';
+import LinkedPipelinesColumnLegacy from '~/pipelines/components/graph/linked_pipelines_column_legacy.vue';
+import graphJSON from './mock_data_legacy';
+import linkedPipelineJSON from './linked_pipelines_mock_data';
+import PipelinesMediator from '~/pipelines/pipeline_details_mediator';
+
+describe('graph component', () => {
+ let store;
+ let mediator;
+ let wrapper;
+
+ const findExpandPipelineBtn = () => wrapper.find('[data-testid="expand-pipeline-button"]');
+ const findAllExpandPipelineBtns = () => wrapper.findAll('[data-testid="expand-pipeline-button"]');
+ const findStageColumns = () => wrapper.findAll(StageColumnComponentLegacy);
+ const findStageColumnAt = i => findStageColumns().at(i);
+
+ beforeEach(() => {
+ mediator = new PipelinesMediator({ endpoint: '' });
+ store = new PipelineStore();
+ store.storePipeline(linkedPipelineJSON);
+
+ setHTMLFixture('<div class="layout-page"></div>');
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('while is loading', () => {
+ it('should render a loading icon', () => {
+ wrapper = mount(GraphComponentLegacy, {
+ propsData: {
+ isLoading: true,
+ pipeline: {},
+ mediator,
+ },
+ });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+ });
+
+ describe('with data', () => {
+ beforeEach(() => {
+ wrapper = mount(GraphComponentLegacy, {
+ propsData: {
+ isLoading: false,
+ pipeline: graphJSON,
+ mediator,
+ },
+ });
+ });
+
+ it('renders the graph', () => {
+ expect(wrapper.find('.js-pipeline-graph').exists()).toBe(true);
+ expect(wrapper.find('.loading-icon').exists()).toBe(false);
+ expect(wrapper.find('.stage-column-list').exists()).toBe(true);
+ });
+
+ it('renders columns in the graph', () => {
+ expect(findStageColumns()).toHaveLength(graphJSON.details.stages.length);
+ });
+ });
+
+ describe('when linked pipelines are present', () => {
+ beforeEach(() => {
+ wrapper = mount(GraphComponentLegacy, {
+ propsData: {
+ isLoading: false,
+ pipeline: store.state.pipeline,
+ mediator,
+ },
+ });
+ });
+
+ describe('rendered output', () => {
+ it('should include the pipelines graph', () => {
+ expect(wrapper.find('.js-pipeline-graph').exists()).toBe(true);
+ });
+
+ it('should not include the loading icon', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ });
+
+ it('should include the stage column', () => {
+ expect(findStageColumnAt(0).exists()).toBe(true);
+ });
+
+ it('stage column should have no-margin, gl-mr-26, has-only-one-job classes if there is only one job', () => {
+ expect(findStageColumnAt(0).classes()).toEqual(
+ expect.arrayContaining(['no-margin', 'gl-mr-26', 'has-only-one-job']),
+ );
+ });
+
+ it('should include the left-margin class on the second child', () => {
+ expect(findStageColumnAt(1).classes('left-margin')).toBe(true);
+ });
+
+ it('should include the left-connector class in the build of the second child', () => {
+ expect(
+ findStageColumnAt(1)
+ .find('.build:nth-child(1)')
+ .classes('left-connector'),
+ ).toBe(true);
+ });
+
+ it('should include the js-has-linked-pipelines flag', () => {
+ expect(wrapper.find('.js-has-linked-pipelines').exists()).toBe(true);
+ });
+ });
+
+ describe('computeds and methods', () => {
+ describe('capitalizeStageName', () => {
+ it('it capitalizes the stage name', () => {
+ expect(
+ wrapper
+ .findAll('.stage-column .stage-name')
+ .at(1)
+ .text(),
+ ).toBe('Prebuild');
+ });
+ });
+
+ describe('stageConnectorClass', () => {
+ it('it returns left-margin when there is a triggerer', () => {
+ expect(findStageColumnAt(1).classes('left-margin')).toBe(true);
+ });
+ });
+ });
+
+ describe('linked pipelines components', () => {
+ beforeEach(() => {
+ wrapper = mount(GraphComponentLegacy, {
+ propsData: {
+ isLoading: false,
+ pipeline: store.state.pipeline,
+ mediator,
+ },
+ });
+ });
+
+ it('should render an upstream pipelines column at first position', () => {
+ expect(wrapper.find(LinkedPipelinesColumnLegacy).exists()).toBe(true);
+ expect(wrapper.find('.stage-column .stage-name').text()).toBe('Upstream');
+ });
+
+ it('should render a downstream pipelines column at last position', () => {
+ const stageColumnNames = wrapper.findAll('.stage-column .stage-name');
+
+ expect(wrapper.find(LinkedPipelinesColumnLegacy).exists()).toBe(true);
+ expect(stageColumnNames.at(stageColumnNames.length - 1).text()).toBe('Downstream');
+ });
+
+ describe('triggered by', () => {
+ describe('on click', () => {
+ it('should emit `onClickUpstreamPipeline` when triggered by linked pipeline is clicked', () => {
+ const btnWrapper = findExpandPipelineBtn();
+
+ btnWrapper.trigger('click');
+
+ btnWrapper.vm.$nextTick(() => {
+ expect(wrapper.emitted().onClickUpstreamPipeline).toEqual([
+ store.state.pipeline.triggered_by,
+ ]);
+ });
+ });
+ });
+
+ describe('with expanded pipeline', () => {
+ it('should render expanded pipeline', done => {
+ // expand the pipeline
+ store.state.pipeline.triggered_by[0].isExpanded = true;
+
+ wrapper = mount(GraphComponentLegacy, {
+ propsData: {
+ isLoading: false,
+ pipeline: store.state.pipeline,
+ mediator,
+ },
+ });
+
+ Vue.nextTick()
+ .then(() => {
+ expect(wrapper.find('.js-upstream-pipeline-12').exists()).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('triggered', () => {
+ describe('on click', () => {
+ it('should emit `onClickTriggered`', () => {
+ // We have to mock this method since we do both style change and
+ // emit and event, not mocking returns an error.
+ wrapper.setMethods({
+ handleClickedDownstream: jest.fn(() =>
+ wrapper.vm.$emit('onClickTriggered', ...store.state.pipeline.triggered),
+ ),
+ });
+
+ const btnWrappers = findAllExpandPipelineBtns();
+ const downstreamBtnWrapper = btnWrappers.at(btnWrappers.length - 1);
+
+ downstreamBtnWrapper.trigger('click');
+
+ downstreamBtnWrapper.vm.$nextTick(() => {
+ expect(wrapper.emitted().onClickTriggered).toEqual([store.state.pipeline.triggered]);
+ });
+ });
+ });
+
+ describe('with expanded pipeline', () => {
+ it('should render expanded pipeline', done => {
+ // expand the pipeline
+ store.state.pipeline.triggered[0].isExpanded = true;
+
+ wrapper = mount(GraphComponentLegacy, {
+ propsData: {
+ isLoading: false,
+ pipeline: store.state.pipeline,
+ mediator,
+ },
+ });
+
+ Vue.nextTick()
+ .then(() => {
+ expect(wrapper.find('.js-downstream-pipeline-34993051')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('when column requests a refresh', () => {
+ beforeEach(() => {
+ findStageColumnAt(0).vm.$emit('refreshPipelineGraph');
+ });
+
+ it('refreshPipelineGraph is emitted', () => {
+ expect(wrapper.emitted().refreshPipelineGraph).toHaveLength(1);
+ });
+ });
+ });
+ });
+ });
+
+ describe('when linked pipelines are not present', () => {
+ beforeEach(() => {
+ const pipeline = Object.assign(linkedPipelineJSON, { triggered: null, triggered_by: null });
+ wrapper = mount(GraphComponentLegacy, {
+ propsData: {
+ isLoading: false,
+ pipeline,
+ mediator,
+ },
+ });
+ });
+
+ describe('rendered output', () => {
+ it('should include the first column with a no margin', () => {
+ const firstColumn = wrapper.find('.stage-column');
+
+ expect(firstColumn.classes('no-margin')).toBe(true);
+ });
+
+ it('should not render a linked pipelines column', () => {
+ expect(wrapper.find('.linked-pipelines-column').exists()).toBe(false);
+ });
+ });
+
+ describe('stageConnectorClass', () => {
+ it('it returns no-margin when no triggerer and there is one job', () => {
+ expect(findStageColumnAt(0).classes('no-margin')).toBe(true);
+ });
+
+ it('it returns left-margin when no triggerer and not the first stage', () => {
+ expect(findStageColumnAt(1).classes('left-margin')).toBe(true);
+ });
+ });
+ });
+
+ describe('capitalizeStageName', () => {
+ it('capitalizes and escapes stage name', () => {
+ wrapper = mount(GraphComponentLegacy, {
+ propsData: {
+ isLoading: false,
+ pipeline: graphJSON,
+ mediator,
+ },
+ });
+
+ expect(findStageColumnAt(1).props('title')).toEqual(
+ 'Deploy &lt;img src=x onerror=alert(document.domain)&gt;',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js
index 5a17be1af23..7572dd83798 100644
--- a/spec/frontend/pipelines/graph/graph_component_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_spec.js
@@ -1,305 +1,83 @@
-import Vue from 'vue';
-import { mount } from '@vue/test-utils';
-import { setHTMLFixture } from 'helpers/fixtures';
-import PipelineStore from '~/pipelines/stores/pipeline_store';
-import graphComponent from '~/pipelines/components/graph/graph_component.vue';
+import { mount, shallowMount } from '@vue/test-utils';
+import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
-import linkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
-import graphJSON from './mock_data';
-import linkedPipelineJSON from './linked_pipelines_mock_data';
-import PipelinesMediator from '~/pipelines/pipeline_details_mediator';
+import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
+import { GRAPHQL } from '~/pipelines/components/graph/constants';
+import {
+ generateResponse,
+ mockPipelineResponse,
+ pipelineWithUpstreamDownstream,
+} from './mock_data';
describe('graph component', () => {
- let store;
- let mediator;
let wrapper;
- const findExpandPipelineBtn = () => wrapper.find('[data-testid="expandPipelineButton"]');
- const findAllExpandPipelineBtns = () => wrapper.findAll('[data-testid="expandPipelineButton"]');
+ const findLinkedColumns = () => wrapper.findAll(LinkedPipelinesColumn);
const findStageColumns = () => wrapper.findAll(StageColumnComponent);
- const findStageColumnAt = i => findStageColumns().at(i);
- beforeEach(() => {
- mediator = new PipelinesMediator({ endpoint: '' });
- store = new PipelineStore();
- store.storePipeline(linkedPipelineJSON);
-
- setHTMLFixture('<div class="layout-page"></div>');
- });
+ const defaultProps = {
+ pipeline: generateResponse(mockPipelineResponse, 'root/fungi-xoxo'),
+ };
+
+ const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => {
+ wrapper = mountFn(PipelineGraph, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ provide: {
+ dataMethod: GRAPHQL,
+ },
+ });
+ };
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
- describe('while is loading', () => {
- it('should render a loading icon', () => {
- wrapper = mount(graphComponent, {
- propsData: {
- isLoading: true,
- pipeline: {},
- mediator,
- },
- });
-
- expect(wrapper.find('.gl-spinner').exists()).toBe(true);
- });
- });
-
describe('with data', () => {
beforeEach(() => {
- wrapper = mount(graphComponent, {
- propsData: {
- isLoading: false,
- pipeline: graphJSON,
- mediator,
- },
- });
+ createComponent({ mountFn: mount });
});
- it('renders the graph', () => {
- expect(wrapper.find('.js-pipeline-graph').exists()).toBe(true);
- expect(wrapper.find('.loading-icon').exists()).toBe(false);
- expect(wrapper.find('.stage-column-list').exists()).toBe(true);
- });
-
- it('renders columns in the graph', () => {
- expect(findStageColumns()).toHaveLength(graphJSON.details.stages.length);
- });
- });
-
- describe('when linked pipelines are present', () => {
- beforeEach(() => {
- wrapper = mount(graphComponent, {
- propsData: {
- isLoading: false,
- pipeline: store.state.pipeline,
- mediator,
- },
- });
- });
-
- describe('rendered output', () => {
- it('should include the pipelines graph', () => {
- expect(wrapper.find('.js-pipeline-graph').exists()).toBe(true);
- });
-
- it('should not include the loading icon', () => {
- expect(wrapper.find('.fa-spinner').exists()).toBe(false);
- });
-
- it('should include the stage column', () => {
- expect(findStageColumnAt(0).exists()).toBe(true);
- });
-
- it('stage column should have no-margin, gl-mr-26, has-only-one-job classes if there is only one job', () => {
- expect(findStageColumnAt(0).classes()).toEqual(
- expect.arrayContaining(['no-margin', 'gl-mr-26', 'has-only-one-job']),
- );
- });
-
- it('should include the left-margin class on the second child', () => {
- expect(findStageColumnAt(1).classes('left-margin')).toBe(true);
- });
-
- it('should include the left-connector class in the build of the second child', () => {
- expect(
- findStageColumnAt(1)
- .find('.build:nth-child(1)')
- .classes('left-connector'),
- ).toBe(true);
- });
-
- it('should include the js-has-linked-pipelines flag', () => {
- expect(wrapper.find('.js-has-linked-pipelines').exists()).toBe(true);
- });
- });
-
- describe('computeds and methods', () => {
- describe('capitalizeStageName', () => {
- it('it capitalizes the stage name', () => {
- expect(
- wrapper
- .findAll('.stage-column .stage-name')
- .at(1)
- .text(),
- ).toBe('Prebuild');
- });
- });
-
- describe('stageConnectorClass', () => {
- it('it returns left-margin when there is a triggerer', () => {
- expect(findStageColumnAt(1).classes('left-margin')).toBe(true);
- });
- });
+ it('renders the main columns in the graph', () => {
+ expect(findStageColumns()).toHaveLength(defaultProps.pipeline.stages.length);
});
- describe('linked pipelines components', () => {
+ describe('when column requests a refresh', () => {
beforeEach(() => {
- wrapper = mount(graphComponent, {
- propsData: {
- isLoading: false,
- pipeline: store.state.pipeline,
- mediator,
- },
- });
+ findStageColumns()
+ .at(0)
+ .vm.$emit('refreshPipelineGraph');
});
- it('should render an upstream pipelines column at first position', () => {
- expect(wrapper.find(linkedPipelinesColumn).exists()).toBe(true);
- expect(wrapper.find('.stage-column .stage-name').text()).toBe('Upstream');
- });
-
- it('should render a downstream pipelines column at last position', () => {
- const stageColumnNames = wrapper.findAll('.stage-column .stage-name');
-
- expect(wrapper.find(linkedPipelinesColumn).exists()).toBe(true);
- expect(stageColumnNames.at(stageColumnNames.length - 1).text()).toBe('Downstream');
- });
-
- describe('triggered by', () => {
- describe('on click', () => {
- it('should emit `onClickUpstreamPipeline` when triggered by linked pipeline is clicked', () => {
- const btnWrapper = findExpandPipelineBtn();
-
- btnWrapper.trigger('click');
-
- btnWrapper.vm.$nextTick(() => {
- expect(wrapper.emitted().onClickUpstreamPipeline).toEqual([
- store.state.pipeline.triggered_by,
- ]);
- });
- });
- });
-
- describe('with expanded pipeline', () => {
- it('should render expanded pipeline', done => {
- // expand the pipeline
- store.state.pipeline.triggered_by[0].isExpanded = true;
-
- wrapper = mount(graphComponent, {
- propsData: {
- isLoading: false,
- pipeline: store.state.pipeline,
- mediator,
- },
- });
-
- Vue.nextTick()
- .then(() => {
- expect(wrapper.find('.js-upstream-pipeline-12').exists()).toBe(true);
- })
- .then(done)
- .catch(done.fail);
- });
- });
- });
-
- describe('triggered', () => {
- describe('on click', () => {
- it('should emit `onClickTriggered`', () => {
- // We have to mock this method since we do both style change and
- // emit and event, not mocking returns an error.
- wrapper.setMethods({
- handleClickedDownstream: jest.fn(() =>
- wrapper.vm.$emit('onClickTriggered', ...store.state.pipeline.triggered),
- ),
- });
-
- const btnWrappers = findAllExpandPipelineBtns();
- const downstreamBtnWrapper = btnWrappers.at(btnWrappers.length - 1);
-
- downstreamBtnWrapper.trigger('click');
-
- downstreamBtnWrapper.vm.$nextTick(() => {
- expect(wrapper.emitted().onClickTriggered).toEqual([store.state.pipeline.triggered]);
- });
- });
- });
-
- describe('with expanded pipeline', () => {
- it('should render expanded pipeline', done => {
- // expand the pipeline
- store.state.pipeline.triggered[0].isExpanded = true;
-
- wrapper = mount(graphComponent, {
- propsData: {
- isLoading: false,
- pipeline: store.state.pipeline,
- mediator,
- },
- });
-
- Vue.nextTick()
- .then(() => {
- expect(wrapper.find('.js-downstream-pipeline-34993051')).not.toBeNull();
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('when column requests a refresh', () => {
- beforeEach(() => {
- findStageColumnAt(0).vm.$emit('refreshPipelineGraph');
- });
-
- it('refreshPipelineGraph is emitted', () => {
- expect(wrapper.emitted().refreshPipelineGraph).toHaveLength(1);
- });
- });
+ it('refreshPipelineGraph is emitted', () => {
+ expect(wrapper.emitted().refreshPipelineGraph).toHaveLength(1);
});
});
});
describe('when linked pipelines are not present', () => {
beforeEach(() => {
- const pipeline = Object.assign(linkedPipelineJSON, { triggered: null, triggered_by: null });
- wrapper = mount(graphComponent, {
- propsData: {
- isLoading: false,
- pipeline,
- mediator,
- },
- });
+ createComponent({ mountFn: mount });
});
- describe('rendered output', () => {
- it('should include the first column with a no margin', () => {
- const firstColumn = wrapper.find('.stage-column');
-
- expect(firstColumn.classes('no-margin')).toBe(true);
- });
-
- it('should not render a linked pipelines column', () => {
- expect(wrapper.find('.linked-pipelines-column').exists()).toBe(false);
- });
- });
-
- describe('stageConnectorClass', () => {
- it('it returns no-margin when no triggerer and there is one job', () => {
- expect(findStageColumnAt(0).classes('no-margin')).toBe(true);
- });
-
- it('it returns left-margin when no triggerer and not the first stage', () => {
- expect(findStageColumnAt(1).classes('left-margin')).toBe(true);
- });
+ it('should not render a linked pipelines column', () => {
+ expect(findLinkedColumns()).toHaveLength(0);
});
});
- describe('capitalizeStageName', () => {
- it('capitalizes and escapes stage name', () => {
- wrapper = mount(graphComponent, {
- propsData: {
- isLoading: false,
- pipeline: graphJSON,
- mediator,
- },
+ describe('when linked pipelines are present', () => {
+ beforeEach(() => {
+ createComponent({
+ mountFn: mount,
+ props: { pipeline: pipelineWithUpstreamDownstream(mockPipelineResponse) },
});
+ });
- expect(findStageColumnAt(1).props('title')).toEqual(
- 'Deploy &lt;img src=x onerror=alert(document.domain)&gt;',
- );
+ it('should render linked pipelines columns', () => {
+ expect(findLinkedColumns()).toHaveLength(2);
});
});
});
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
new file mode 100644
index 00000000000..875aaa48037
--- /dev/null
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -0,0 +1,124 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { shallowMount } from '@vue/test-utils';
+import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
+import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue';
+import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
+import getPipelineDetails from '~/pipelines/graphql/queries/get_pipeline_details.query.graphql';
+import { mockPipelineResponse } from './mock_data';
+
+const defaultProvide = {
+ pipelineProjectPath: 'frog/amphibirama',
+ pipelineIid: '22',
+};
+
+describe('Pipeline graph wrapper', () => {
+ Vue.use(VueApollo);
+
+ let wrapper;
+ const getAlert = () => wrapper.find(GlAlert);
+ const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const getGraph = () => wrapper.find(PipelineGraph);
+
+ const createComponent = ({
+ apolloProvider,
+ data = {},
+ provide = defaultProvide,
+ mountFn = shallowMount,
+ } = {}) => {
+ wrapper = mountFn(PipelineGraphWrapper, {
+ provide,
+ apolloProvider,
+ data() {
+ return {
+ ...data,
+ };
+ },
+ });
+ };
+
+ const createComponentWithApollo = (
+ getPipelineDetailsHandler = jest.fn().mockResolvedValue(mockPipelineResponse),
+ ) => {
+ const requestHandlers = [[getPipelineDetails, getPipelineDetailsHandler]];
+
+ const apolloProvider = createMockApollo(requestHandlers);
+ createComponent({ apolloProvider });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when data is loading', () => {
+ it('displays the loading icon', () => {
+ createComponentWithApollo();
+ expect(getLoadingIcon().exists()).toBe(true);
+ });
+
+ it('does not display the alert', () => {
+ createComponentWithApollo();
+ expect(getAlert().exists()).toBe(false);
+ });
+
+ it('does not display the graph', () => {
+ createComponentWithApollo();
+ expect(getGraph().exists()).toBe(false);
+ });
+ });
+
+ describe('when data has loaded', () => {
+ beforeEach(async () => {
+ createComponentWithApollo();
+ jest.runOnlyPendingTimers();
+ await wrapper.vm.$nextTick();
+ });
+
+ it('does not display the loading icon', () => {
+ expect(getLoadingIcon().exists()).toBe(false);
+ });
+
+ it('does not display the alert', () => {
+ expect(getAlert().exists()).toBe(false);
+ });
+
+ it('displays the graph', () => {
+ expect(getGraph().exists()).toBe(true);
+ });
+ });
+
+ describe('when there is an error', () => {
+ beforeEach(async () => {
+ createComponentWithApollo(jest.fn().mockRejectedValue(new Error('GraphQL error')));
+ jest.runOnlyPendingTimers();
+ await wrapper.vm.$nextTick();
+ });
+
+ it('does not display the loading icon', () => {
+ expect(getLoadingIcon().exists()).toBe(false);
+ });
+
+ it('displays the alert', () => {
+ expect(getAlert().exists()).toBe(true);
+ });
+
+ it('does not display the graph', () => {
+ expect(getGraph().exists()).toBe(false);
+ });
+ });
+
+ describe('when refresh action is emitted', () => {
+ beforeEach(async () => {
+ createComponentWithApollo();
+ jest.spyOn(wrapper.vm.$apollo.queries.pipeline, 'refetch');
+ await wrapper.vm.$nextTick();
+ getGraph().vm.$emit('refreshPipelineGraph');
+ });
+
+ it('calls refetch', () => {
+ expect(wrapper.vm.$apollo.queries.pipeline.refetch).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index 67986ca7739..fb005d628a9 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -17,7 +17,7 @@ describe('Linked pipeline', () => {
const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findPipelineLink = () => wrapper.find('[data-testid="pipelineLink"]');
- const findExpandButton = () => wrapper.find('[data-testid="expandPipelineButton"]');
+ const findExpandButton = () => wrapper.find('[data-testid="expand-pipeline-button"]');
const createWrapper = (propsData, data = []) => {
wrapper = mount(LinkedPipelineComponent, {
@@ -40,20 +40,13 @@ describe('Linked pipeline', () => {
projectId: invalidTriggeredPipelineId,
columnTitle: 'Downstream',
type: DOWNSTREAM,
+ expanded: false,
};
beforeEach(() => {
createWrapper(props);
});
- it('should render a list item as the containing element', () => {
- expect(wrapper.element.tagName).toBe('LI');
- });
-
- it('should render a button', () => {
- expect(findButton().exists()).toBe(true);
- });
-
it('should render the project name', () => {
expect(wrapper.text()).toContain(props.pipeline.project.name);
});
@@ -105,12 +98,14 @@ describe('Linked pipeline', () => {
projectId: validTriggeredPipelineId,
columnTitle: 'Downstream',
type: DOWNSTREAM,
+ expanded: false,
};
const upstreamProps = {
...downstreamProps,
columnTitle: 'Upstream',
type: UPSTREAM,
+ expanded: false,
};
it('parent/child label container should exist', () => {
@@ -173,7 +168,7 @@ describe('Linked pipeline', () => {
`(
'$pipelineType.columnTitle pipeline button icon should be $anglePosition if expanded state is $expanded',
({ pipelineType, anglePosition, expanded }) => {
- createWrapper(pipelineType, { expanded });
+ createWrapper({ ...pipelineType, expanded });
expect(findExpandButton().props('icon')).toBe(anglePosition);
},
);
@@ -185,6 +180,7 @@ describe('Linked pipeline', () => {
projectId: invalidTriggeredPipelineId,
columnTitle: 'Downstream',
type: DOWNSTREAM,
+ expanded: false,
};
beforeEach(() => {
@@ -202,6 +198,7 @@ describe('Linked pipeline', () => {
projectId: validTriggeredPipelineId,
columnTitle: 'Downstream',
type: DOWNSTREAM,
+ expanded: false,
};
beforeEach(() => {
@@ -219,10 +216,7 @@ describe('Linked pipeline', () => {
jest.spyOn(wrapper.vm.$root, '$emit');
findButton().trigger('click');
- expect(wrapper.vm.$root.$emit.mock.calls[0]).toEqual([
- 'bv::hide::tooltip',
- 'js-linked-pipeline-34993051',
- ]);
+ expect(wrapper.vm.$root.$emit.mock.calls[0]).toEqual(['bv::hide::tooltip']);
});
it('should emit downstreamHovered with job name on mouseover', () => {
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js
new file mode 100644
index 00000000000..b6c700c65d2
--- /dev/null
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js
@@ -0,0 +1,40 @@
+import { shallowMount } from '@vue/test-utils';
+import LinkedPipelinesColumnLegacy from '~/pipelines/components/graph/linked_pipelines_column_legacy.vue';
+import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue';
+import { UPSTREAM } from '~/pipelines/components/graph/constants';
+import mockData from './linked_pipelines_mock_data';
+
+describe('Linked Pipelines Column', () => {
+ const propsData = {
+ columnTitle: 'Upstream',
+ linkedPipelines: mockData.triggered,
+ graphPosition: 'right',
+ projectId: 19,
+ type: UPSTREAM,
+ };
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(LinkedPipelinesColumnLegacy, { propsData });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the pipeline orientation', () => {
+ const titleElement = wrapper.find('.linked-pipelines-column-title');
+
+ expect(titleElement.text()).toBe(propsData.columnTitle);
+ });
+
+ it('renders the correct number of linked pipelines', () => {
+ const linkedPipelineElements = wrapper.findAll(LinkedPipeline);
+
+ expect(linkedPipelineElements.length).toBe(propsData.linkedPipelines.length);
+ });
+
+ it('renders cross project triangle when column is upstream', () => {
+ expect(wrapper.find('.cross-project-triangle').exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
index e6ae3154d1d..37eb5f900dd 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
@@ -1,40 +1,120 @@
-import { shallowMount } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
+import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue';
-import { UPSTREAM } from '~/pipelines/components/graph/constants';
-import mockData from './linked_pipelines_mock_data';
+import getPipelineDetails from '~/pipelines/graphql/queries/get_pipeline_details.query.graphql';
+import { DOWNSTREAM, GRAPHQL } from '~/pipelines/components/graph/constants';
+import { LOAD_FAILURE } from '~/pipelines/constants';
+import {
+ mockPipelineResponse,
+ pipelineWithUpstreamDownstream,
+ wrappedPipelineReturn,
+} from './mock_data';
+
+const processedPipeline = pipelineWithUpstreamDownstream(mockPipelineResponse);
describe('Linked Pipelines Column', () => {
- const propsData = {
+ const defaultProps = {
columnTitle: 'Upstream',
- linkedPipelines: mockData.triggered,
- graphPosition: 'right',
- projectId: 19,
- type: UPSTREAM,
+ linkedPipelines: processedPipeline.downstream,
+ type: DOWNSTREAM,
};
+
let wrapper;
+ const findLinkedColumnTitle = () => wrapper.find('[data-testid="linked-column-title"]');
+ const findLinkedPipelineElements = () => wrapper.findAll(LinkedPipeline);
+ const findPipelineGraph = () => wrapper.find(PipelineGraph);
+ const findExpandButton = () => wrapper.find('[data-testid="expand-pipeline-button"]');
- beforeEach(() => {
- wrapper = shallowMount(LinkedPipelinesColumn, { propsData });
- });
+ const localVue = createLocalVue();
+ localVue.use(VueApollo);
+
+ const createComponent = ({ apolloProvider, mountFn = shallowMount, props = {} } = {}) => {
+ wrapper = mountFn(LinkedPipelinesColumn, {
+ apolloProvider,
+ localVue,
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ provide: {
+ dataMethod: GRAPHQL,
+ },
+ });
+ };
+
+ const createComponentWithApollo = (
+ mountFn = shallowMount,
+ getPipelineDetailsHandler = jest.fn().mockResolvedValue(wrappedPipelineReturn),
+ ) => {
+ const requestHandlers = [[getPipelineDetails, getPipelineDetailsHandler]];
+
+ const apolloProvider = createMockApollo(requestHandlers);
+ createComponent({ apolloProvider, mountFn });
+ };
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
- it('renders the pipeline orientation', () => {
- const titleElement = wrapper.find('.linked-pipelines-column-title');
+ describe('it renders correctly', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the pipeline title', () => {
+ expect(findLinkedColumnTitle().text()).toBe(defaultProps.columnTitle);
+ });
- expect(titleElement.text()).toBe(propsData.columnTitle);
+ it('renders the correct number of linked pipelines', () => {
+ expect(findLinkedPipelineElements()).toHaveLength(defaultProps.linkedPipelines.length);
+ });
});
- it('renders the correct number of linked pipelines', () => {
- const linkedPipelineElements = wrapper.findAll(LinkedPipeline);
+ describe('click action', () => {
+ const clickExpandButton = async () => {
+ await findExpandButton().trigger('click');
+ await wrapper.vm.$nextTick();
+ };
- expect(linkedPipelineElements.length).toBe(propsData.linkedPipelines.length);
- });
+ const clickExpandButtonAndAwaitTimers = async () => {
+ await clickExpandButton();
+ jest.runOnlyPendingTimers();
+ await wrapper.vm.$nextTick();
+ };
+
+ describe('when successful', () => {
+ beforeEach(() => {
+ createComponentWithApollo(mount);
+ });
+
+ it('toggles the pipeline visibility', async () => {
+ expect(findPipelineGraph().exists()).toBe(false);
+ await clickExpandButtonAndAwaitTimers();
+ expect(findPipelineGraph().exists()).toBe(true);
+ await clickExpandButton();
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
+ });
+
+ describe('on error', () => {
+ beforeEach(() => {
+ createComponentWithApollo(mount, jest.fn().mockRejectedValue(new Error('GraphQL error')));
+ });
+
+ it('emits the error', async () => {
+ await clickExpandButton();
+ expect(wrapper.emitted().error).toEqual([[LOAD_FAILURE]]);
+ });
- it('renders cross project triangle when column is upstream', () => {
- expect(wrapper.find('.cross-project-triangle').exists()).toBe(true);
+ it('does not show the pipeline', async () => {
+ expect(findPipelineGraph().exists()).toBe(false);
+ await clickExpandButtonAndAwaitTimers();
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
+ });
});
});
diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js
index a4a5d78f906..d53a11eea0e 100644
--- a/spec/frontend/pipelines/graph/mock_data.js
+++ b/spec/frontend/pipelines/graph/mock_data.js
@@ -1,261 +1,665 @@
-export default {
- id: 123,
- user: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: null,
- web_url: 'http://localhost:3000/root',
- },
- active: false,
- coverage: null,
- path: '/root/ci-mock/pipelines/123',
- details: {
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/pipelines/123',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- },
- duration: 9,
- finished_at: '2017-04-19T14:30:27.542Z',
- stages: [
- {
- name: 'test',
- title: 'test: passed',
- groups: [
- {
- name: 'test',
- size: 1,
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/builds/4153',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/builds/4153/retry',
- method: 'post',
+import { unwrapPipelineData } from '~/pipelines/components/graph/utils';
+
+export const mockPipelineResponse = {
+ data: {
+ project: {
+ __typename: 'Project',
+ pipeline: {
+ __typename: 'Pipeline',
+ id: 163,
+ iid: '22',
+ downstream: null,
+ upstream: null,
+ stages: {
+ __typename: 'CiStageConnection',
+ nodes: [
+ {
+ __typename: 'CiStage',
+ name: 'build',
+ status: {
+ __typename: 'DetailedStatus',
+ action: null,
},
- },
- jobs: [
- {
- id: 4153,
- name: 'test',
- build_path: '/root/ci-mock/builds/4153',
- retry_path: '/root/ci-mock/builds/4153/retry',
- playable: false,
- created_at: '2017-04-13T09:25:18.959Z',
- updated_at: '2017-04-13T09:25:23.118Z',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/builds/4153',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/builds/4153/retry',
- method: 'post',
+ groups: {
+ __typename: 'CiGroupConnection',
+ nodes: [
+ {
+ __typename: 'CiGroup',
+ name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
+ size: 1,
+ status: {
+ __typename: 'DetailedStatus',
+ label: 'passed',
+ group: 'success',
+ icon: 'status_success',
+ },
+ jobs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/jobs/1482',
+ group: 'success',
+ action: {
+ __typename: 'StatusAction',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ path: '/root/abcd-dag/-/jobs/1482/retry',
+ title: 'Retry',
+ },
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [],
+ },
+ },
+ ],
+ },
},
- },
- },
- ],
- },
- ],
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/pipelines/123#test',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- },
- path: '/root/ci-mock/pipelines/123#test',
- dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=test',
- },
- {
- name: 'deploy <img src=x onerror=alert(document.domain)>',
- title: 'deploy: passed',
- groups: [
- {
- name: 'deploy to production',
- size: 1,
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/builds/4166',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/builds/4166/retry',
- method: 'post',
- },
- },
- jobs: [
- {
- id: 4166,
- name: 'deploy to production',
- build_path: '/root/ci-mock/builds/4166',
- retry_path: '/root/ci-mock/builds/4166/retry',
- playable: false,
- created_at: '2017-04-19T14:29:46.463Z',
- updated_at: '2017-04-19T14:30:27.498Z',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/builds/4166',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/builds/4166/retry',
- method: 'post',
+ {
+ __typename: 'CiGroup',
+ name: 'build_b',
+ size: 1,
+ status: {
+ __typename: 'DetailedStatus',
+ label: 'passed',
+ group: 'success',
+ icon: 'status_success',
+ },
+ jobs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'build_b',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/jobs/1515',
+ group: 'success',
+ action: {
+ __typename: 'StatusAction',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ path: '/root/abcd-dag/-/jobs/1515/retry',
+ title: 'Retry',
+ },
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [],
+ },
+ },
+ ],
+ },
},
- },
- },
- ],
- },
- {
- name: 'deploy to staging',
- size: 1,
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/builds/4159',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/builds/4159/retry',
- method: 'post',
+ {
+ __typename: 'CiGroup',
+ name: 'build_c',
+ size: 1,
+ status: {
+ __typename: 'DetailedStatus',
+ label: 'passed',
+ group: 'success',
+ icon: 'status_success',
+ },
+ jobs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'build_c',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/jobs/1484',
+ group: 'success',
+ action: {
+ __typename: 'StatusAction',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ path: '/root/abcd-dag/-/jobs/1484/retry',
+ title: 'Retry',
+ },
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [],
+ },
+ },
+ ],
+ },
+ },
+ {
+ __typename: 'CiGroup',
+ name: 'build_d',
+ size: 3,
+ status: {
+ __typename: 'DetailedStatus',
+ label: 'passed',
+ group: 'success',
+ icon: 'status_success',
+ },
+ jobs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'build_d 1/3',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/jobs/1485',
+ group: 'success',
+ action: {
+ __typename: 'StatusAction',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ path: '/root/abcd-dag/-/jobs/1485/retry',
+ title: 'Retry',
+ },
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [],
+ },
+ },
+ {
+ __typename: 'CiJob',
+ name: 'build_d 2/3',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/jobs/1486',
+ group: 'success',
+ action: {
+ __typename: 'StatusAction',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ path: '/root/abcd-dag/-/jobs/1486/retry',
+ title: 'Retry',
+ },
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [],
+ },
+ },
+ {
+ __typename: 'CiJob',
+ name: 'build_d 3/3',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/jobs/1487',
+ group: 'success',
+ action: {
+ __typename: 'StatusAction',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ path: '/root/abcd-dag/-/jobs/1487/retry',
+ title: 'Retry',
+ },
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
},
},
- jobs: [
- {
- id: 4159,
- name: 'deploy to staging',
- build_path: '/root/ci-mock/builds/4159',
- retry_path: '/root/ci-mock/builds/4159/retry',
- playable: false,
- created_at: '2017-04-18T16:32:08.420Z',
- updated_at: '2017-04-18T16:32:12.631Z',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/builds/4159',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/builds/4159/retry',
- method: 'post',
+ {
+ __typename: 'CiStage',
+ name: 'test',
+ status: {
+ __typename: 'DetailedStatus',
+ action: null,
+ },
+ groups: {
+ __typename: 'CiGroupConnection',
+ nodes: [
+ {
+ __typename: 'CiGroup',
+ name: 'test_a',
+ size: 1,
+ status: {
+ __typename: 'DetailedStatus',
+ label: 'passed',
+ group: 'success',
+ icon: 'status_success',
+ },
+ jobs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'test_a',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/jobs/1514',
+ group: 'success',
+ action: {
+ __typename: 'StatusAction',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ path: '/root/abcd-dag/-/jobs/1514/retry',
+ title: 'Retry',
+ },
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'build_c',
+ },
+ {
+ __typename: 'CiJob',
+ name: 'build_b',
+ },
+ {
+ __typename: 'CiJob',
+ name:
+ 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ __typename: 'CiGroup',
+ name: 'test_b',
+ size: 2,
+ status: {
+ __typename: 'DetailedStatus',
+ label: 'passed',
+ group: 'success',
+ icon: 'status_success',
+ },
+ jobs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'test_b 1/2',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/jobs/1489',
+ group: 'success',
+ action: {
+ __typename: 'StatusAction',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ path: '/root/abcd-dag/-/jobs/1489/retry',
+ title: 'Retry',
+ },
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'build_d 3/3',
+ },
+ {
+ __typename: 'CiJob',
+ name: 'build_d 2/3',
+ },
+ {
+ __typename: 'CiJob',
+ name: 'build_d 1/3',
+ },
+ {
+ __typename: 'CiJob',
+ name: 'build_b',
+ },
+ {
+ __typename: 'CiJob',
+ name:
+ 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
+ },
+ ],
+ },
+ },
+ {
+ __typename: 'CiJob',
+ name: 'test_b 2/2',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/jobs/1490',
+ group: 'success',
+ action: {
+ __typename: 'StatusAction',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ path: '/root/abcd-dag/-/jobs/1490/retry',
+ title: 'Retry',
+ },
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'build_d 3/3',
+ },
+ {
+ __typename: 'CiJob',
+ name: 'build_d 2/3',
+ },
+ {
+ __typename: 'CiJob',
+ name: 'build_d 1/3',
+ },
+ {
+ __typename: 'CiJob',
+ name: 'build_b',
+ },
+ {
+ __typename: 'CiJob',
+ name:
+ 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ __typename: 'CiGroup',
+ name: 'test_c',
+ size: 1,
+ status: {
+ __typename: 'DetailedStatus',
+ label: null,
+ group: 'success',
+ icon: 'status_success',
+ },
+ jobs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'test_c',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: null,
+ hasDetails: true,
+ detailsPath: '/root/kinder-pipe/-/pipelines/154',
+ group: 'success',
+ action: null,
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'build_c',
+ },
+ {
+ __typename: 'CiJob',
+ name: 'build_b',
+ },
+ {
+ __typename: 'CiJob',
+ name:
+ 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ __typename: 'CiGroup',
+ name: 'test_d',
+ size: 1,
+ status: {
+ __typename: 'DetailedStatus',
+ label: null,
+ group: 'success',
+ icon: 'status_success',
+ },
+ jobs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'test_d',
+ scheduledAt: null,
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_success',
+ tooltip: null,
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/pipelines/153',
+ group: 'success',
+ action: null,
+ },
+ needs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ name: 'build_b',
+ },
+ ],
+ },
+ },
+ ],
+ },
},
- },
+ ],
},
- ],
- },
- ],
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/pipelines/123#deploy',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ },
+ ],
},
- path: '/root/ci-mock/pipelines/123#deploy',
- dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=deploy',
- },
- ],
- artifacts: [],
- manual_actions: [
- {
- name: 'deploy to production',
- path: '/root/ci-mock/builds/4166/play',
- playable: false,
},
- ],
+ },
},
- flags: {
- latest: true,
- triggered: false,
- stuck: false,
- yaml_errors: false,
- retryable: false,
- cancelable: false,
+};
+
+export const downstream = {
+ nodes: [
+ {
+ id: 175,
+ iid: '31',
+ path: '/root/elemenohpee/-/pipelines/175',
+ status: {
+ group: 'success',
+ label: 'passed',
+ icon: 'status_success',
+ __typename: 'DetailedStatus',
+ },
+ sourceJob: {
+ name: 'test_c',
+ __typename: 'CiJob',
+ },
+ project: {
+ id: 'gid://gitlab/Project/25',
+ name: 'elemenohpee',
+ fullPath: 'root/elemenohpee',
+ __typename: 'Project',
+ },
+ __typename: 'Pipeline',
+ multiproject: true,
+ },
+ {
+ id: 181,
+ iid: '27',
+ path: '/root/abcd-dag/-/pipelines/181',
+ status: {
+ group: 'success',
+ label: 'passed',
+ icon: 'status_success',
+ __typename: 'DetailedStatus',
+ },
+ sourceJob: {
+ name: 'test_d',
+ __typename: 'CiJob',
+ },
+ project: {
+ id: 'gid://gitlab/Project/23',
+ name: 'abcd-dag',
+ fullPath: 'root/abcd-dag',
+ __typename: 'Project',
+ },
+ __typename: 'Pipeline',
+ multiproject: false,
+ },
+ ],
+};
+
+export const upstream = {
+ id: 161,
+ iid: '24',
+ path: '/root/abcd-dag/-/pipelines/161',
+ status: {
+ group: 'success',
+ label: 'passed',
+ icon: 'status_success',
+ __typename: 'DetailedStatus',
},
- ref: {
- name: 'master',
- path: '/root/ci-mock/tree/master',
- tag: false,
- branch: true,
+ sourceJob: null,
+ project: {
+ id: 'gid://gitlab/Project/23',
+ name: 'abcd-dag',
+ fullPath: 'root/abcd-dag',
+ __typename: 'Project',
},
- commit: {
- id: '798e5f902592192afaba73f4668ae30e56eae492',
- short_id: '798e5f90',
- title: "Merge branch 'new-branch' into 'master'\r",
- created_at: '2017-04-13T10:25:17.000+01:00',
- parent_ids: [
- '54d483b1ed156fbbf618886ddf7ab023e24f8738',
- 'c8e2d38a6c538822e81c57022a6e3a0cfedebbcc',
- ],
- message:
- "Merge branch 'new-branch' into 'master'\r\n\r\nAdd new file\r\n\r\nSee merge request !1",
- author_name: 'Root',
- author_email: 'admin@example.com',
- authored_date: '2017-04-13T10:25:17.000+01:00',
- committer_name: 'Root',
- committer_email: 'admin@example.com',
- committed_date: '2017-04-13T10:25:17.000+01:00',
- author: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: null,
- web_url: 'http://localhost:3000/root',
+ __typename: 'Pipeline',
+ multiproject: true,
+};
+
+export const wrappedPipelineReturn = {
+ data: {
+ project: {
+ pipeline: {
+ id: 'gid://gitlab/Ci::Pipeline/175',
+ iid: '38',
+ downstream: {
+ nodes: [],
+ },
+ upstream: {
+ id: 'gid://gitlab/Ci::Pipeline/174',
+ iid: '37',
+ path: '/root/elemenohpee/-/pipelines/174',
+ status: {
+ group: 'success',
+ label: 'passed',
+ icon: 'status_success',
+ },
+ sourceJob: {
+ name: 'test_c',
+ },
+ project: {
+ id: 'gid://gitlab/Project/25',
+ name: 'elemenohpee',
+ fullPath: 'root/elemenohpee',
+ },
+ },
+ stages: {
+ nodes: [
+ {
+ name: 'build',
+ status: {
+ action: null,
+ },
+ groups: {
+ nodes: [
+ {
+ status: {
+ label: 'passed',
+ group: 'success',
+ icon: 'status_success',
+ },
+ name: 'build_n',
+ size: 1,
+ jobs: {
+ nodes: [
+ {
+ name: 'build_n',
+ scheduledAt: null,
+ needs: {
+ nodes: [],
+ },
+ status: {
+ icon: 'status_success',
+ tooltip: 'passed',
+ hasDetails: true,
+ detailsPath: '/root/elemenohpee/-/jobs/1662',
+ group: 'success',
+ action: {
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ path: '/root/elemenohpee/-/jobs/1662/retry',
+ title: 'Retry',
+ },
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
},
- author_gravatar_url: null,
- commit_url:
- 'http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492',
- commit_path: '/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492',
},
- created_at: '2017-04-13T09:25:18.881Z',
- updated_at: '2017-04-19T14:30:27.561Z',
+};
+
+export const generateResponse = (raw, mockPath) => unwrapPipelineData(mockPath, raw.data);
+
+export const pipelineWithUpstreamDownstream = base => {
+ const pip = { ...base };
+ pip.data.project.pipeline.downstream = downstream;
+ pip.data.project.pipeline.upstream = upstream;
+
+ return generateResponse(pip, 'root/abcd-dag');
};
diff --git a/spec/frontend/pipelines/graph/mock_data_legacy.js b/spec/frontend/pipelines/graph/mock_data_legacy.js
new file mode 100644
index 00000000000..a4a5d78f906
--- /dev/null
+++ b/spec/frontend/pipelines/graph/mock_data_legacy.js
@@ -0,0 +1,261 @@
+export default {
+ id: 123,
+ user: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url: null,
+ web_url: 'http://localhost:3000/root',
+ },
+ active: false,
+ coverage: null,
+ path: '/root/ci-mock/pipelines/123',
+ details: {
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/pipelines/123',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ },
+ duration: 9,
+ finished_at: '2017-04-19T14:30:27.542Z',
+ stages: [
+ {
+ name: 'test',
+ title: 'test: passed',
+ groups: [
+ {
+ name: 'test',
+ size: 1,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4153',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4153/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 4153,
+ name: 'test',
+ build_path: '/root/ci-mock/builds/4153',
+ retry_path: '/root/ci-mock/builds/4153/retry',
+ playable: false,
+ created_at: '2017-04-13T09:25:18.959Z',
+ updated_at: '2017-04-13T09:25:23.118Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4153',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4153/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/pipelines/123#test',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ },
+ path: '/root/ci-mock/pipelines/123#test',
+ dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=test',
+ },
+ {
+ name: 'deploy <img src=x onerror=alert(document.domain)>',
+ title: 'deploy: passed',
+ groups: [
+ {
+ name: 'deploy to production',
+ size: 1,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4166',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4166/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 4166,
+ name: 'deploy to production',
+ build_path: '/root/ci-mock/builds/4166',
+ retry_path: '/root/ci-mock/builds/4166/retry',
+ playable: false,
+ created_at: '2017-04-19T14:29:46.463Z',
+ updated_at: '2017-04-19T14:30:27.498Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4166',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4166/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ {
+ name: 'deploy to staging',
+ size: 1,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4159',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4159/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 4159,
+ name: 'deploy to staging',
+ build_path: '/root/ci-mock/builds/4159',
+ retry_path: '/root/ci-mock/builds/4159/retry',
+ playable: false,
+ created_at: '2017-04-18T16:32:08.420Z',
+ updated_at: '2017-04-18T16:32:12.631Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4159',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4159/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/pipelines/123#deploy',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ },
+ path: '/root/ci-mock/pipelines/123#deploy',
+ dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=deploy',
+ },
+ ],
+ artifacts: [],
+ manual_actions: [
+ {
+ name: 'deploy to production',
+ path: '/root/ci-mock/builds/4166/play',
+ playable: false,
+ },
+ ],
+ },
+ flags: {
+ latest: true,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: false,
+ cancelable: false,
+ },
+ ref: {
+ name: 'master',
+ path: '/root/ci-mock/tree/master',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: '798e5f902592192afaba73f4668ae30e56eae492',
+ short_id: '798e5f90',
+ title: "Merge branch 'new-branch' into 'master'\r",
+ created_at: '2017-04-13T10:25:17.000+01:00',
+ parent_ids: [
+ '54d483b1ed156fbbf618886ddf7ab023e24f8738',
+ 'c8e2d38a6c538822e81c57022a6e3a0cfedebbcc',
+ ],
+ message:
+ "Merge branch 'new-branch' into 'master'\r\n\r\nAdd new file\r\n\r\nSee merge request !1",
+ author_name: 'Root',
+ author_email: 'admin@example.com',
+ authored_date: '2017-04-13T10:25:17.000+01:00',
+ committer_name: 'Root',
+ committer_email: 'admin@example.com',
+ committed_date: '2017-04-13T10:25:17.000+01:00',
+ author: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url: null,
+ web_url: 'http://localhost:3000/root',
+ },
+ author_gravatar_url: null,
+ commit_url:
+ 'http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492',
+ commit_path: '/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492',
+ },
+ created_at: '2017-04-13T09:25:18.881Z',
+ updated_at: '2017-04-19T14:30:27.561Z',
+};
diff --git a/spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js b/spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js
new file mode 100644
index 00000000000..463e4c12c7d
--- /dev/null
+++ b/spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js
@@ -0,0 +1,135 @@
+import { shallowMount } from '@vue/test-utils';
+import StageColumnComponentLegacy from '~/pipelines/components/graph/stage_column_component_legacy.vue';
+
+describe('stage column component', () => {
+ const mockJob = {
+ id: 4250,
+ name: 'test',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ details_path: '/root/ci-mock/builds/4250',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4250/retry',
+ method: 'post',
+ },
+ },
+ };
+
+ let wrapper;
+
+ beforeEach(() => {
+ const mockGroups = [];
+ for (let i = 0; i < 3; i += 1) {
+ const mockedJob = { ...mockJob };
+ mockedJob.id += i;
+ mockGroups.push(mockedJob);
+ }
+
+ wrapper = shallowMount(StageColumnComponentLegacy, {
+ propsData: {
+ title: 'foo',
+ groups: mockGroups,
+ hasTriggeredBy: false,
+ },
+ });
+ });
+
+ it('should render provided title', () => {
+ expect(
+ wrapper
+ .find('.stage-name')
+ .text()
+ .trim(),
+ ).toBe('foo');
+ });
+
+ it('should render the provided groups', () => {
+ expect(wrapper.findAll('.builds-container > ul > li').length).toBe(
+ wrapper.props('groups').length,
+ );
+ });
+
+ describe('jobId', () => {
+ it('escapes job name', () => {
+ wrapper = shallowMount(StageColumnComponentLegacy, {
+ propsData: {
+ groups: [
+ {
+ id: 4259,
+ name: '<img src=x onerror=alert(document.domain)>',
+ status: {
+ icon: 'status_success',
+ label: 'success',
+ tooltip: '<img src=x onerror=alert(document.domain)>',
+ },
+ },
+ ],
+ title: 'test',
+ hasTriggeredBy: false,
+ },
+ });
+
+ expect(wrapper.find('.builds-container li').attributes('id')).toBe(
+ 'ci-badge-&lt;img src=x onerror=alert(document.domain)&gt;',
+ );
+ });
+ });
+
+ describe('with action', () => {
+ it('renders action button', () => {
+ wrapper = shallowMount(StageColumnComponentLegacy, {
+ propsData: {
+ groups: [
+ {
+ id: 4259,
+ name: '<img src=x onerror=alert(document.domain)>',
+ status: {
+ icon: 'status_success',
+ label: 'success',
+ tooltip: '<img src=x onerror=alert(document.domain)>',
+ },
+ },
+ ],
+ title: 'test',
+ hasTriggeredBy: false,
+ action: {
+ icon: 'play',
+ title: 'Play all',
+ path: 'action',
+ },
+ },
+ });
+
+ expect(wrapper.find('.js-stage-action').exists()).toBe(true);
+ });
+ });
+
+ describe('without action', () => {
+ it('does not render action button', () => {
+ wrapper = shallowMount(StageColumnComponentLegacy, {
+ propsData: {
+ groups: [
+ {
+ id: 4259,
+ name: '<img src=x onerror=alert(document.domain)>',
+ status: {
+ icon: 'status_success',
+ label: 'success',
+ tooltip: '<img src=x onerror=alert(document.domain)>',
+ },
+ },
+ ],
+ title: 'test',
+ hasTriggeredBy: false,
+ },
+ });
+
+ expect(wrapper.find('.js-stage-action').exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/graph/stage_column_component_spec.js b/spec/frontend/pipelines/graph/stage_column_component_spec.js
index d32534326c5..44803929f6d 100644
--- a/spec/frontend/pipelines/graph/stage_column_component_spec.js
+++ b/spec/frontend/pipelines/graph/stage_column_component_spec.js
@@ -1,64 +1,101 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
+import ActionComponent from '~/pipelines/components/graph/action_component.vue';
+import JobItem from '~/pipelines/components/graph/job_item.vue';
+import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
-import stageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
-
-describe('stage column component', () => {
- const mockJob = {
- id: 4250,
- name: 'test',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- details_path: '/root/ci-mock/builds/4250',
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/builds/4250/retry',
- method: 'post',
- },
+const mockJob = {
+ id: 4250,
+ name: 'test',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ details_path: '/root/ci-mock/builds/4250',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4250/retry',
+ method: 'post',
},
- };
+ },
+};
+const mockGroups = Array(4)
+ .fill(0)
+ .map((item, idx) => {
+ return { ...mockJob, id: idx, name: `fish-${idx}` };
+ });
+
+const defaultProps = {
+ title: 'Fish',
+ groups: mockGroups,
+};
+
+describe('stage column component', () => {
let wrapper;
- beforeEach(() => {
- const mockGroups = [];
- for (let i = 0; i < 3; i += 1) {
- const mockedJob = { ...mockJob };
- mockedJob.id += i;
- mockGroups.push(mockedJob);
- }
+ const findStageColumnTitle = () => wrapper.find('[data-testid="stage-column-title"]');
+ const findStageColumnGroup = () => wrapper.find('[data-testid="stage-column-group"]');
+ const findAllStageColumnGroups = () => wrapper.findAll('[data-testid="stage-column-group"]');
+ const findJobItem = () => wrapper.find(JobItem);
+ const findActionComponent = () => wrapper.find(ActionComponent);
- wrapper = shallowMount(stageColumnComponent, {
+ const createComponent = ({ method = shallowMount, props = {} } = {}) => {
+ wrapper = method(StageColumnComponent, {
propsData: {
- title: 'foo',
- groups: mockGroups,
- hasTriggeredBy: false,
+ ...defaultProps,
+ ...props,
},
});
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
});
- it('should render provided title', () => {
- expect(
- wrapper
- .find('.stage-name')
- .text()
- .trim(),
- ).toBe('foo');
+ describe('when mounted', () => {
+ beforeEach(() => {
+ createComponent({ method: mount });
+ });
+
+ it('should render provided title', () => {
+ expect(findStageColumnTitle().text()).toBe(defaultProps.title);
+ });
+
+ it('should render the provided groups', () => {
+ expect(findAllStageColumnGroups().length).toBe(mockGroups.length);
+ });
});
- it('should render the provided groups', () => {
- expect(wrapper.findAll('.builds-container > ul > li').length).toBe(
- wrapper.props('groups').length,
- );
+ describe('when job notifies action is complete', () => {
+ beforeEach(() => {
+ createComponent({
+ method: mount,
+ props: {
+ groups: [
+ {
+ title: 'Fish',
+ size: 1,
+ jobs: [mockJob],
+ },
+ ],
+ },
+ });
+ findJobItem().vm.$emit('pipelineActionRequestComplete');
+ });
+
+ it('emits refreshPipelineGraph', () => {
+ expect(wrapper.emitted().refreshPipelineGraph).toHaveLength(1);
+ });
});
- describe('jobId', () => {
- it('escapes job name', () => {
- wrapper = shallowMount(stageColumnComponent, {
- propsData: {
+ describe('job', () => {
+ beforeEach(() => {
+ createComponent({
+ method: mount,
+ props: {
groups: [
{
id: 4259,
@@ -70,21 +107,29 @@ describe('stage column component', () => {
},
},
],
- title: 'test',
- hasTriggeredBy: false,
+ title: 'test <img src=x onerror=alert(document.domain)>',
},
});
+ });
- expect(wrapper.find('.builds-container li').attributes('id')).toBe(
+ it('capitalizes and escapes name', () => {
+ expect(findStageColumnTitle().text()).toBe(
+ 'Test &lt;img src=x onerror=alert(document.domain)&gt;',
+ );
+ });
+
+ it('escapes id', () => {
+ expect(findStageColumnGroup().attributes('id')).toBe(
'ci-badge-&lt;img src=x onerror=alert(document.domain)&gt;',
);
});
});
describe('with action', () => {
- it('renders action button', () => {
- wrapper = shallowMount(stageColumnComponent, {
- propsData: {
+ beforeEach(() => {
+ createComponent({
+ method: mount,
+ props: {
groups: [
{
id: 4259,
@@ -105,15 +150,18 @@ describe('stage column component', () => {
},
},
});
+ });
- expect(wrapper.find('.js-stage-action').exists()).toBe(true);
+ it('renders action button', () => {
+ expect(findActionComponent().exists()).toBe(true);
});
});
describe('without action', () => {
- it('does not render action button', () => {
- wrapper = shallowMount(stageColumnComponent, {
- propsData: {
+ beforeEach(() => {
+ createComponent({
+ method: mount,
+ props: {
groups: [
{
id: 4259,
@@ -129,8 +177,10 @@ describe('stage column component', () => {
hasTriggeredBy: false,
},
});
+ });
- expect(wrapper.find('.js-stage-action').exists()).toBe(false);
+ it('does not render action button', () => {
+ expect(findActionComponent().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/pipelines/pipeline_graph/gitlab_ci_yaml_visualization_spec.js b/spec/frontend/pipelines/pipeline_graph/gitlab_ci_yaml_visualization_spec.js
deleted file mode 100644
index fea42350959..00000000000
--- a/spec/frontend/pipelines/pipeline_graph/gitlab_ci_yaml_visualization_spec.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlTab } from '@gitlab/ui';
-import { yamlString } from './mock_data';
-import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
-import GitlabCiYamlVisualization from '~/pipelines/components/pipeline_graph/gitlab_ci_yaml_visualization.vue';
-
-describe('gitlab yaml visualization component', () => {
- const defaultProps = { blobData: yamlString };
- let wrapper;
-
- const createComponent = props => {
- return shallowMount(GitlabCiYamlVisualization, {
- propsData: {
- ...defaultProps,
- ...props,
- },
- });
- };
-
- const findGlTabComponents = () => wrapper.findAll(GlTab);
- const findPipelineGraph = () => wrapper.find(PipelineGraph);
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('tabs component', () => {
- beforeEach(() => {
- wrapper = createComponent();
- });
-
- it('renders the file and visualization tabs', () => {
- expect(findGlTabComponents()).toHaveLength(2);
- });
- });
-
- describe('graph component', () => {
- beforeEach(() => {
- wrapper = createComponent();
- });
-
- it('is hidden by default', () => {
- expect(findPipelineGraph().exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/pipelines/pipeline_graph/mock_data.js b/spec/frontend/pipelines/pipeline_graph/mock_data.js
index 4f55fdd6b28..a77973b293c 100644
--- a/spec/frontend/pipelines/pipeline_graph/mock_data.js
+++ b/spec/frontend/pipelines/pipeline_graph/mock_data.js
@@ -1,4 +1,4 @@
-import { createUniqueJobId } from '~/pipelines/utils';
+import { createUniqueLinkId } from '~/pipelines/utils';
export const yamlString = `stages:
- empty
@@ -41,10 +41,10 @@ deploy_a:
script: echo hello
`;
-const jobId1 = createUniqueJobId('build', 'build_1');
-const jobId2 = createUniqueJobId('test', 'test_1');
-const jobId3 = createUniqueJobId('test', 'test_2');
-const jobId4 = createUniqueJobId('deploy', 'deploy_1');
+const jobId1 = createUniqueLinkId('build', 'build_1');
+const jobId2 = createUniqueLinkId('test', 'test_1');
+const jobId3 = createUniqueLinkId('test', 'test_2');
+const jobId4 = createUniqueLinkId('deploy', 'deploy_1');
export const pipelineData = {
stages: [
diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
index 7c8ebc27974..6704ee06c1a 100644
--- a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
+++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
@@ -1,5 +1,8 @@
import { shallowMount } from '@vue/test-utils';
+import { GlAlert } from '@gitlab/ui';
import { pipelineData, singleStageData } from './mock_data';
+import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants';
+import { DRAW_FAILURE, EMPTY_PIPELINE_DATA, INVALID_CI_CONFIG } from '~/pipelines/constants';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import StagePill from '~/pipelines/components/pipeline_graph/stage_pill.vue';
import JobPill from '~/pipelines/components/pipeline_graph/job_pill.vue';
@@ -8,15 +11,16 @@ describe('pipeline graph component', () => {
const defaultProps = { pipelineData };
let wrapper;
- const createComponent = props => {
+ const createComponent = (props = defaultProps) => {
return shallowMount(PipelineGraph, {
propsData: {
- ...defaultProps,
...props,
},
});
};
+ const findPipelineGraph = () => wrapper.find('[data-testid="graph-container"]');
+ const findAlert = () => wrapper.find(GlAlert);
const findAllStagePills = () => wrapper.findAll(StagePill);
const findAllStageBackgroundElements = () => wrapper.findAll('[data-testid="stage-background"]');
const findStageBackgroundElementAt = index => findAllStageBackgroundElements().at(index);
@@ -33,54 +37,92 @@ describe('pipeline graph component', () => {
});
it('renders an empty section', () => {
- expect(wrapper.text()).toContain(
- 'The visualization will appear in this tab when the CI/CD configuration file is populated with valid syntax.',
- );
+ expect(wrapper.text()).toBe(wrapper.vm.$options.warningTexts[EMPTY_PIPELINE_DATA]);
+ expect(findPipelineGraph().exists()).toBe(false);
expect(findAllStagePills()).toHaveLength(0);
expect(findAllJobPills()).toHaveLength(0);
});
});
- describe('with data', () => {
+ describe('with `INVALID` status', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ pipelineData: { status: CI_CONFIG_STATUS_INVALID } });
+ });
+
+ it('renders an error message and does not render the graph', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(wrapper.vm.$options.warningTexts[INVALID_CI_CONFIG]);
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
+ });
+
+ describe('without `INVALID` status', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ it('renders the graph with no status error', () => {
+ expect(findAlert().text()).not.toBe(wrapper.vm.$options.warningTexts[INVALID_CI_CONFIG]);
+ expect(findPipelineGraph().exists()).toBe(true);
+ });
+ });
+
+ describe('with error while rendering the links', () => {
beforeEach(() => {
wrapper = createComponent();
});
+ it('renders the error that link could not be drawn', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[DRAW_FAILURE]);
+ });
+ });
+
+ describe('with only one stage', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ pipelineData: singleStageData });
+ });
+
it('renders the right number of stage pills', () => {
- const expectedStagesLength = pipelineData.stages.length;
+ const expectedStagesLength = singleStageData.stages.length;
expect(findAllStagePills()).toHaveLength(expectedStagesLength);
});
- it.each`
- cssClass | expectedState
- ${'gl-rounded-bottom-left-6'} | ${true}
- ${'gl-rounded-top-left-6'} | ${true}
- ${'gl-rounded-top-right-6'} | ${false}
- ${'gl-rounded-bottom-right-6'} | ${false}
- `(
- 'rounds corner: $class should be $expectedState on the first element',
- ({ cssClass, expectedState }) => {
+ it('renders the right number of job pills', () => {
+ // We count the number of jobs in the mock data
+ const expectedJobsLength = singleStageData.stages.reduce((acc, val) => {
+ return acc + val.groups.length;
+ }, 0);
+
+ expect(findAllJobPills()).toHaveLength(expectedJobsLength);
+ });
+
+ describe('rounds corner', () => {
+ it.each`
+ cssClass | expectedState
+ ${'gl-rounded-bottom-left-6'} | ${true}
+ ${'gl-rounded-top-left-6'} | ${true}
+ ${'gl-rounded-top-right-6'} | ${true}
+ ${'gl-rounded-bottom-right-6'} | ${true}
+ `('$cssClass should be $expectedState on the only element', ({ cssClass, expectedState }) => {
const classes = findStageBackgroundElementAt(0).classes();
expect(classes.includes(cssClass)).toBe(expectedState);
- },
- );
-
- it.each`
- cssClass | expectedState
- ${'gl-rounded-bottom-left-6'} | ${false}
- ${'gl-rounded-top-left-6'} | ${false}
- ${'gl-rounded-top-right-6'} | ${true}
- ${'gl-rounded-bottom-right-6'} | ${true}
- `(
- 'rounds corner: $class should be $expectedState on the last element',
- ({ cssClass, expectedState }) => {
- const classes = findStageBackgroundElementAt(pipelineData.stages.length - 1).classes();
+ });
+ });
+ });
- expect(classes.includes(cssClass)).toBe(expectedState);
- },
- );
+ describe('with multiple stages and jobs', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ it('renders the right number of stage pills', () => {
+ const expectedStagesLength = pipelineData.stages.length;
+
+ expect(findAllStagePills()).toHaveLength(expectedStagesLength);
+ });
it('renders the right number of job pills', () => {
// We count the number of jobs in the mock data
@@ -90,26 +132,34 @@ describe('pipeline graph component', () => {
expect(findAllJobPills()).toHaveLength(expectedJobsLength);
});
- });
- describe('with only one stage', () => {
- beforeEach(() => {
- wrapper = createComponent({ pipelineData: singleStageData });
- });
+ describe('rounds corner', () => {
+ it.each`
+ cssClass | expectedState
+ ${'gl-rounded-bottom-left-6'} | ${true}
+ ${'gl-rounded-top-left-6'} | ${true}
+ ${'gl-rounded-top-right-6'} | ${false}
+ ${'gl-rounded-bottom-right-6'} | ${false}
+ `(
+ '$cssClass should be $expectedState on the first element',
+ ({ cssClass, expectedState }) => {
+ const classes = findStageBackgroundElementAt(0).classes();
+
+ expect(classes.includes(cssClass)).toBe(expectedState);
+ },
+ );
- it.each`
- cssClass | expectedState
- ${'gl-rounded-bottom-left-6'} | ${true}
- ${'gl-rounded-top-left-6'} | ${true}
- ${'gl-rounded-top-right-6'} | ${true}
- ${'gl-rounded-bottom-right-6'} | ${true}
- `(
- 'rounds corner: $class should be $expectedState on the only element',
- ({ cssClass, expectedState }) => {
- const classes = findStageBackgroundElementAt(0).classes();
+ it.each`
+ cssClass | expectedState
+ ${'gl-rounded-bottom-left-6'} | ${false}
+ ${'gl-rounded-top-left-6'} | ${false}
+ ${'gl-rounded-top-right-6'} | ${true}
+ ${'gl-rounded-bottom-right-6'} | ${true}
+ `('$cssClass should be $expectedState on the last element', ({ cssClass, expectedState }) => {
+ const classes = findStageBackgroundElementAt(pipelineData.stages.length - 1).classes();
expect(classes.includes(cssClass)).toBe(expectedState);
- },
- );
+ });
+ });
});
});
diff --git a/spec/frontend/pipelines/pipeline_graph/utils_spec.js b/spec/frontend/pipelines/pipeline_graph/utils_spec.js
index ade026c7053..12154df6fcf 100644
--- a/spec/frontend/pipelines/pipeline_graph/utils_spec.js
+++ b/spec/frontend/pipelines/pipeline_graph/utils_spec.js
@@ -1,19 +1,24 @@
-import {
- preparePipelineGraphData,
- createUniqueJobId,
- generateJobNeedsDict,
-} from '~/pipelines/utils';
+import { createJobsHash, generateJobNeedsDict } from '~/pipelines/utils';
describe('utils functions', () => {
- const emptyResponse = { stages: [], jobs: {} };
const jobName1 = 'build_1';
const jobName2 = 'build_2';
const jobName3 = 'test_1';
const jobName4 = 'deploy_1';
- const job1 = { script: 'echo hello', stage: 'build' };
- const job2 = { script: 'echo build', stage: 'build' };
- const job3 = { script: 'echo test', stage: 'test', needs: [jobName1, jobName2] };
- const job4 = { script: 'echo deploy', stage: 'deploy', needs: [jobName3] };
+ const job1 = { name: jobName1, script: 'echo hello', stage: 'build' };
+ const job2 = { name: jobName2, script: 'echo build', stage: 'build' };
+ const job3 = {
+ name: jobName3,
+ script: 'echo test',
+ stage: 'test',
+ needs: [jobName1, jobName2],
+ };
+ const job4 = {
+ name: jobName4,
+ script: 'echo deploy',
+ stage: 'deploy',
+ needs: [jobName3],
+ };
const userDefinedStage = 'myStage';
const pipelineGraphData = {
@@ -28,7 +33,6 @@ describe('utils functions', () => {
{
name: jobName4,
jobs: [{ ...job4 }],
- id: createUniqueJobId(job4.stage, jobName4),
},
],
},
@@ -38,12 +42,10 @@ describe('utils functions', () => {
{
name: jobName1,
jobs: [{ ...job1 }],
- id: createUniqueJobId(job1.stage, jobName1),
},
{
name: jobName2,
jobs: [{ ...job2 }],
- id: createUniqueJobId(job2.stage, jobName2),
},
],
},
@@ -53,158 +55,59 @@ describe('utils functions', () => {
{
name: jobName3,
jobs: [{ ...job3 }],
- id: createUniqueJobId(job3.stage, jobName3),
},
],
},
],
- jobs: {
- [jobName1]: { ...job1, id: createUniqueJobId(job1.stage, jobName1) },
- [jobName2]: { ...job2, id: createUniqueJobId(job2.stage, jobName2) },
- [jobName3]: { ...job3, id: createUniqueJobId(job3.stage, jobName3) },
- [jobName4]: { ...job4, id: createUniqueJobId(job4.stage, jobName4) },
- },
};
- describe('preparePipelineGraphData', () => {
- describe('returns an empty array of stages and empty job objects if', () => {
- it('no data is passed', () => {
- expect(preparePipelineGraphData({})).toEqual(emptyResponse);
- });
-
- it('no stages are found', () => {
- expect(preparePipelineGraphData({ includes: 'template/myTemplate.gitlab-ci.yml' })).toEqual(
- emptyResponse,
- );
- });
+ describe('createJobsHash', () => {
+ it('returns an empty object if there are no jobs received as argument', () => {
+ expect(createJobsHash([])).toEqual({});
});
- describe('returns the correct array of stages and object of jobs', () => {
- it('when multiple jobs are in the same stage', () => {
- const expectedData = {
- stages: [
- {
- name: job1.stage,
- groups: [
- {
- name: jobName1,
- jobs: [{ ...job1 }],
- id: createUniqueJobId(job1.stage, jobName1),
- },
- {
- name: jobName2,
- jobs: [{ ...job2 }],
- id: createUniqueJobId(job2.stage, jobName2),
- },
- ],
- },
- ],
- jobs: {
- [jobName1]: { ...job1, id: createUniqueJobId(job1.stage, jobName1) },
- [jobName2]: { ...job2, id: createUniqueJobId(job2.stage, jobName2) },
- },
- };
- expect(
- preparePipelineGraphData({ [jobName1]: { ...job1 }, [jobName2]: { ...job2 } }),
- ).toEqual(expectedData);
- });
-
- it('when stages are defined by the user', () => {
- const userDefinedStage2 = 'myStage2';
-
- const expectedData = {
- stages: [
- {
- name: userDefinedStage,
- groups: [],
- },
- {
- name: userDefinedStage2,
- groups: [],
- },
- ],
- jobs: {},
- };
-
- expect(preparePipelineGraphData({ stages: [userDefinedStage, userDefinedStage2] })).toEqual(
- expectedData,
- );
- });
-
- it('by combining user defined stage and job stages, it preserves user defined order', () => {
- const userDefinedStageThatOverlaps = 'deploy';
-
- expect(
- preparePipelineGraphData({
- stages: [userDefinedStage, userDefinedStageThatOverlaps],
- [jobName1]: { ...job1 },
- [jobName2]: { ...job2 },
- [jobName3]: { ...job3 },
- [jobName4]: { ...job4 },
- }),
- ).toEqual(pipelineGraphData);
- });
-
- it('with only unique values', () => {
- const expectedData = {
- stages: [
- {
- name: job1.stage,
- groups: [
- {
- name: jobName1,
- jobs: [{ ...job1 }],
- id: createUniqueJobId(job1.stage, jobName1),
- },
- ],
- },
- ],
- jobs: {
- [jobName1]: { ...job1, id: createUniqueJobId(job1.stage, jobName1) },
- },
- };
+ it('returns a hash with the jobname as key and all its data as value', () => {
+ const jobs = {
+ [jobName1]: job1,
+ [jobName2]: job2,
+ [jobName3]: job3,
+ [jobName4]: job4,
+ };
- expect(
- preparePipelineGraphData({
- stages: ['build'],
- [jobName1]: { ...job1 },
- [jobName1]: { ...job1 },
- }),
- ).toEqual(expectedData);
- });
+ expect(createJobsHash(pipelineGraphData.stages)).toEqual(jobs);
});
});
describe('generateJobNeedsDict', () => {
it('generates an empty object if it receives no jobs', () => {
- expect(generateJobNeedsDict({ jobs: {} })).toEqual({});
+ expect(generateJobNeedsDict({})).toEqual({});
});
it('generates a dict with empty needs if there are no dependencies', () => {
const smallGraph = {
- jobs: {
- [jobName1]: { ...job1, id: createUniqueJobId(job1.stage, jobName1) },
- [jobName2]: { ...job2, id: createUniqueJobId(job2.stage, jobName2) },
- },
+ [jobName1]: job1,
+ [jobName2]: job2,
};
expect(generateJobNeedsDict(smallGraph)).toEqual({
- [pipelineGraphData.jobs[jobName1].id]: [],
- [pipelineGraphData.jobs[jobName2].id]: [],
+ [jobName1]: [],
+ [jobName2]: [],
});
});
it('generates a dict where key is the a job and its value is an array of all its needs', () => {
- const uniqueJobName1 = pipelineGraphData.jobs[jobName1].id;
- const uniqueJobName2 = pipelineGraphData.jobs[jobName2].id;
- const uniqueJobName3 = pipelineGraphData.jobs[jobName3].id;
- const uniqueJobName4 = pipelineGraphData.jobs[jobName4].id;
+ const jobsWithNeeds = {
+ [jobName1]: job1,
+ [jobName2]: job2,
+ [jobName3]: job3,
+ [jobName4]: job4,
+ };
- expect(generateJobNeedsDict(pipelineGraphData)).toEqual({
- [uniqueJobName1]: [],
- [uniqueJobName2]: [],
- [uniqueJobName3]: [uniqueJobName1, uniqueJobName2],
- [uniqueJobName4]: [uniqueJobName3, uniqueJobName1, uniqueJobName2],
+ expect(generateJobNeedsDict(jobsWithNeeds)).toEqual({
+ [jobName1]: [],
+ [jobName2]: [],
+ [jobName3]: [jobName1, jobName2],
+ [jobName4]: [jobName3, jobName1, jobName2],
});
});
});
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index 0bcc3f96f7c..fc45af2c254 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -16,6 +16,7 @@ describe('Pipeline Url Component', () => {
const findAutoDevopsTag = () => wrapper.find('[data-testid="pipeline-url-autodevops"]');
const findStuckTag = () => wrapper.find('[data-testid="pipeline-url-stuck"]');
const findDetachedTag = () => wrapper.find('[data-testid="pipeline-url-detached"]');
+ const findForkTag = () => wrapper.find('[data-testid="pipeline-url-fork"]');
const defaultProps = {
pipeline: {
@@ -30,6 +31,9 @@ describe('Pipeline Url Component', () => {
const createComponent = props => {
wrapper = shallowMount(PipelineUrlComponent, {
propsData: { ...defaultProps, ...props },
+ provide: {
+ targetProjectFullPath: 'test/test',
+ },
});
};
@@ -137,4 +141,15 @@ describe('Pipeline Url Component', () => {
expect(findScheduledTag().exists()).toBe(true);
expect(findScheduledTag().text()).toContain('Scheduled');
});
+ it('should render the fork badge when the pipeline was run in a fork', () => {
+ createComponent({
+ pipeline: {
+ flags: {},
+ project: { fullPath: 'test/forked' },
+ },
+ });
+
+ expect(findForkTag().exists()).toBe(true);
+ expect(findForkTag().text()).toBe('fork');
+ });
});
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index a272803f9b6..ce0e76ba22d 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -31,7 +31,7 @@ describe('Pipelines', () => {
const paths = {
endpoint: 'twitter/flight/pipelines.json',
- autoDevopsPath: '/help/topics/autodevops/index.md',
+ autoDevopsHelpPath: '/help/topics/autodevops/index.md',
helpPagePath: '/help/ci/quick_start/README',
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
@@ -43,7 +43,7 @@ describe('Pipelines', () => {
const noPermissions = {
endpoint: 'twitter/flight/pipelines.json',
- autoDevopsPath: '/help/topics/autodevops/index.md',
+ autoDevopsHelpPath: '/help/topics/autodevops/index.md',
helpPagePath: '/help/ci/quick_start/README',
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
diff --git a/spec/frontend/pipelines/test_reports/stores/getters_spec.js b/spec/frontend/pipelines/test_reports/stores/getters_spec.js
index 58e8065033f..8cef499fdb9 100644
--- a/spec/frontend/pipelines/test_reports/stores/getters_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/getters_spec.js
@@ -10,11 +10,19 @@ describe('Getters TestReports Store', () => {
const defaultState = {
testReports,
selectedSuiteIndex: 0,
+ pageInfo: {
+ page: 1,
+ perPage: 2,
+ },
};
const emptyState = {
testReports: {},
selectedSuite: null,
+ pageInfo: {
+ page: 1,
+ perPage: 2,
+ },
};
beforeEach(() => {
@@ -59,15 +67,17 @@ describe('Getters TestReports Store', () => {
});
describe('getSuiteTests', () => {
- it('should return the test cases inside the suite', () => {
+ it('should return the current page of test cases inside the suite', () => {
setupState();
const cases = getters.getSuiteTests(state);
- const expected = testReports.test_suites[0].test_cases.map(x => ({
- ...x,
- formattedTime: formattedTime(x.execution_time),
- icon: iconForTestStatus(x.status),
- }));
+ const expected = testReports.test_suites[0].test_cases
+ .map(x => ({
+ ...x,
+ formattedTime: formattedTime(x.execution_time),
+ icon: iconForTestStatus(x.status),
+ }))
+ .slice(0, state.pageInfo.perPage);
expect(cases).toEqual(expected);
});
@@ -78,4 +88,15 @@ describe('Getters TestReports Store', () => {
expect(getters.getSuiteTests(state)).toEqual([]);
});
});
+
+ describe('getSuiteTestCount', () => {
+ it('should return the total number of test cases', () => {
+ setupState();
+
+ const testCount = getters.getSuiteTestCount(state);
+ const expected = testReports.test_suites[0].test_cases.length;
+
+ expect(testCount).toEqual(expected);
+ });
+ });
});
diff --git a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
index b935029bc6a..191e9e7391c 100644
--- a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
@@ -12,12 +12,25 @@ describe('Mutations TestReports Store', () => {
testReports: {},
selectedSuite: null,
isLoading: false,
+ pageInfo: {
+ page: 1,
+ perPage: 2,
+ },
};
beforeEach(() => {
mockState = { ...defaultState };
});
+ describe('set page', () => {
+ it('should set the current page to display', () => {
+ const pageToDisplay = 3;
+ mutations[types.SET_PAGE](mockState, pageToDisplay);
+
+ expect(mockState.pageInfo.page).toEqual(pageToDisplay);
+ });
+ });
+
describe('set suite', () => {
it('should set the suite at the given index', () => {
mockState.testReports = testReports;
diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
index 284099b000b..0e00ca670a7 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -1,7 +1,7 @@
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { getJSONFixture } from 'helpers/fixtures';
-import { GlButton, GlFriendlyWrap } from '@gitlab/ui';
+import { GlButton, GlFriendlyWrap, GlPagination } from '@gitlab/ui';
import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue';
import * as getters from '~/pipelines/stores/test_reports/getters';
import { TestStatus } from '~/pipelines/constants';
@@ -26,13 +26,17 @@ describe('Test reports suite table', () => {
const findCaseRowAtIndex = index => wrapper.findAll('.js-case-row').at(index);
const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`);
- const createComponent = (suite = testSuite) => {
+ const createComponent = (suite = testSuite, perPage = 20) => {
store = new Vuex.Store({
state: {
testReports: {
test_suites: [suite],
},
selectedSuiteIndex: 0,
+ pageInfo: {
+ page: 1,
+ perPage,
+ },
},
getters,
});
@@ -86,4 +90,20 @@ describe('Test reports suite table', () => {
expect(button.attributes('data-clipboard-text')).toBe(file);
});
});
+
+ describe('when a test suite has more test cases than the pagination size', () => {
+ const perPage = 2;
+
+ beforeEach(() => {
+ createComponent(testSuite, perPage);
+ });
+
+ it('renders one page of test cases', () => {
+ expect(allCaseRows().length).toBe(perPage);
+ });
+
+ it('renders a pagination component', () => {
+ expect(wrapper.find(GlPagination).exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
index b53955ab743..1db736ba01e 100644
--- a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
@@ -1,18 +1,12 @@
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { stubComponent } from 'helpers/stub_component';
import PipelineStatusToken from '~/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue';
describe('Pipeline Status Token', () => {
let wrapper;
- const stubs = {
- GlFilteredSearchToken: {
- props: GlFilteredSearchToken.props,
- template: `<div><slot name="suggestions"></slot></div>`,
- },
- };
-
- const findFilteredSearchToken = () => wrapper.find(stubs.GlFilteredSearchToken);
+ const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
const findAllGlIcons = () => wrapper.findAll(GlIcon);
@@ -33,7 +27,11 @@ describe('Pipeline Status Token', () => {
propsData: {
...defaultProps,
},
- stubs,
+ stubs: {
+ GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
+ template: `<div><slot name="suggestions"></slot></div>`,
+ }),
+ },
});
};
diff --git a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
index 9363944a719..375325c0c6a 100644
--- a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
@@ -1,4 +1,5 @@
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
+import { stubComponent } from 'helpers/stub_component';
import { shallowMount } from '@vue/test-utils';
import Api from '~/api';
import PipelineTriggerAuthorToken from '~/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue';
@@ -7,14 +8,7 @@ import { users } from '../mock_data';
describe('Pipeline Trigger Author Token', () => {
let wrapper;
- const stubs = {
- GlFilteredSearchToken: {
- props: GlFilteredSearchToken.props,
- template: `<div><slot name="suggestions"></slot></div>`,
- },
- };
-
- const findFilteredSearchToken = () => wrapper.find(stubs.GlFilteredSearchToken);
+ const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
@@ -42,7 +36,11 @@ describe('Pipeline Trigger Author Token', () => {
...data,
};
},
- stubs,
+ stubs: {
+ GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
+ template: `<div><slot name="suggestions"></slot></div>`,
+ }),
+ },
});
};
diff --git a/spec/frontend/pipelines/unwrapping_utils_spec.js b/spec/frontend/pipelines/unwrapping_utils_spec.js
new file mode 100644
index 00000000000..3533599611f
--- /dev/null
+++ b/spec/frontend/pipelines/unwrapping_utils_spec.js
@@ -0,0 +1,151 @@
+import {
+ unwrapArrayOfJobs,
+ unwrapGroups,
+ unwrapNodesWithName,
+ unwrapStagesWithNeeds,
+} from '~/pipelines/components/unwrapping_utils';
+
+const groupsArray = [
+ {
+ name: 'build_a',
+ size: 1,
+ status: {
+ label: 'passed',
+ group: 'success',
+ icon: 'status_success',
+ },
+ },
+ {
+ name: 'bob_the_build',
+ size: 1,
+ status: {
+ label: 'passed',
+ group: 'success',
+ icon: 'status_success',
+ },
+ },
+];
+
+const basicStageInfo = {
+ name: 'center_stage',
+ status: {
+ action: null,
+ },
+};
+
+const stagesAndGroups = [
+ {
+ ...basicStageInfo,
+ groups: {
+ nodes: groupsArray,
+ },
+ },
+];
+
+const needArray = [
+ {
+ name: 'build_b',
+ },
+];
+
+const elephantArray = [
+ {
+ name: 'build_b',
+ elephant: 'gray',
+ },
+];
+
+const baseJobs = {
+ name: 'test_d',
+ status: {
+ icon: 'status_success',
+ tooltip: null,
+ hasDetails: true,
+ detailsPath: '/root/abcd-dag/-/pipelines/162',
+ group: 'success',
+ action: null,
+ },
+};
+
+const jobArrayWithNeeds = [
+ {
+ ...baseJobs,
+ needs: {
+ nodes: needArray,
+ },
+ },
+];
+
+const jobArrayWithElephant = [
+ {
+ ...baseJobs,
+ needs: {
+ nodes: elephantArray,
+ },
+ },
+];
+
+const completeMock = [
+ {
+ ...basicStageInfo,
+ groups: {
+ nodes: groupsArray.map(group => ({ ...group, jobs: { nodes: jobArrayWithNeeds } })),
+ },
+ },
+];
+
+describe('Shared pipeline unwrapping utils', () => {
+ describe('unwrapArrayOfJobs', () => {
+ it('returns an empty array if the input is an empty undefined', () => {
+ expect(unwrapArrayOfJobs(undefined)).toEqual([]);
+ });
+
+ it('returns an empty array if the input is an empty array', () => {
+ expect(unwrapArrayOfJobs([])).toEqual([]);
+ });
+
+ it('returns a flatten array of each job with their data and stage name', () => {
+ expect(
+ unwrapArrayOfJobs([
+ { name: 'build', groups: [{ name: 'job_a_1' }, { name: 'job_a_2' }] },
+ { name: 'test', groups: [{ name: 'job_b' }] },
+ ]),
+ ).toMatchObject([
+ { category: 'build', name: 'job_a_1' },
+ { category: 'build', name: 'job_a_2' },
+ { category: 'test', name: 'job_b' },
+ ]);
+ });
+ });
+
+ describe('unwrapGroups', () => {
+ it('takes stages without nodes and returns the unwrapped groups', () => {
+ expect(unwrapGroups(stagesAndGroups)[0].groups).toEqual(groupsArray);
+ });
+
+ it('keeps other stage properties intact', () => {
+ expect(unwrapGroups(stagesAndGroups)[0]).toMatchObject(basicStageInfo);
+ });
+ });
+
+ describe('unwrapNodesWithName', () => {
+ it('works with no field argument', () => {
+ expect(unwrapNodesWithName(jobArrayWithNeeds, 'needs')[0].needs).toEqual([needArray[0].name]);
+ });
+
+ it('works with custom field argument', () => {
+ expect(unwrapNodesWithName(jobArrayWithElephant, 'needs', 'elephant')[0].needs).toEqual([
+ elephantArray[0].elephant,
+ ]);
+ });
+ });
+
+ describe('unwrapStagesWithNeeds', () => {
+ it('removes nodes from groups, jobs, and needs', () => {
+ const firstProcessedGroup = unwrapStagesWithNeeds(completeMock)[0].groups[0];
+ expect(firstProcessedGroup).toMatchObject(groupsArray[0]);
+ expect(firstProcessedGroup.jobs[0]).toMatchObject(baseJobs);
+ expect(firstProcessedGroup.jobs[0].needs[0]).toBe(needArray[0].name);
+ });
+ });
+});
diff --git a/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap b/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap
index ac87fe893b9..c7e760486c0 100644
--- a/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap
+++ b/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`StatisticsList matches the snapshot 1`] = `
+exports[`StatisticsList displays the counts data with labels 1`] = `
<ul>
<li>
<span>
@@ -35,7 +35,7 @@ exports[`StatisticsList matches the snapshot 1`] = `
</span>
<strong>
- 50%
+ 50.00%
</strong>
</li>
<li>
diff --git a/spec/frontend/projects/pipelines/charts/components/app_legacy_spec.js b/spec/frontend/projects/pipelines/charts/components/app_legacy_spec.js
new file mode 100644
index 00000000000..c03b571eb26
--- /dev/null
+++ b/spec/frontend/projects/pipelines/charts/components/app_legacy_spec.js
@@ -0,0 +1,72 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlColumnChart } from '@gitlab/ui/dist/charts';
+import Component from '~/projects/pipelines/charts/components/app_legacy.vue';
+import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue';
+import PipelinesAreaChart from '~/projects/pipelines/charts/components/pipelines_area_chart.vue';
+import {
+ counts,
+ timesChartData,
+ areaChartData as lastWeekChartData,
+ areaChartData as lastMonthChartData,
+ lastYearChartData,
+} from '../mock_data';
+
+describe('ProjectsPipelinesChartsApp', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(Component, {
+ propsData: {
+ counts,
+ timesChartData,
+ lastWeekChartData,
+ lastMonthChartData,
+ lastYearChartData,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('overall statistics', () => {
+ it('displays the statistics list', () => {
+ const list = wrapper.find(StatisticsList);
+
+ expect(list.exists()).toBeTruthy();
+ expect(list.props('counts')).toBe(counts);
+ });
+
+ it('displays the commit duration chart', () => {
+ const chart = wrapper.find(GlColumnChart);
+
+ expect(chart.exists()).toBeTruthy();
+ expect(chart.props('yAxisTitle')).toBe('Minutes');
+ expect(chart.props('xAxisTitle')).toBe('Commit');
+ expect(chart.props('bars')).toBe(wrapper.vm.timesChartTransformedData);
+ expect(chart.props('option')).toBe(wrapper.vm.$options.timesChartOptions);
+ });
+ });
+
+ describe('pipelines charts', () => {
+ it('displays 3 area charts', () => {
+ expect(wrapper.findAll(PipelinesAreaChart).length).toBe(3);
+ });
+
+ describe('displays individual correctly', () => {
+ it('renders with the correct data', () => {
+ const charts = wrapper.findAll(PipelinesAreaChart);
+
+ for (let i = 0; i < charts.length; i += 1) {
+ const chart = charts.at(i);
+
+ expect(chart.exists()).toBeTruthy();
+ expect(chart.props('chartData')).toBe(wrapper.vm.areaCharts[i].data);
+ expect(chart.text()).toBe(wrapper.vm.areaCharts[i].title);
+ }
+ });
+ });
+ });
+});
diff --git a/spec/frontend/projects/pipelines/charts/components/app_spec.js b/spec/frontend/projects/pipelines/charts/components/app_spec.js
index 0dd3407dbbc..f8737dda5f6 100644
--- a/spec/frontend/projects/pipelines/charts/components/app_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/app_spec.js
@@ -1,29 +1,45 @@
-import { shallowMount } from '@vue/test-utils';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
import Component from '~/projects/pipelines/charts/components/app.vue';
import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue';
import PipelinesAreaChart from '~/projects/pipelines/charts/components/pipelines_area_chart.vue';
-import {
- counts,
- timesChartData,
- areaChartData as lastWeekChartData,
- areaChartData as lastMonthChartData,
- lastYearChartData,
-} from '../mock_data';
+import getPipelineCountByStatus from '~/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql';
+import getProjectPipelineStatistics from '~/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql';
+import { mockPipelineCount, mockPipelineStatistics } from '../mock_data';
+
+const projectPath = 'gitlab-org/gitlab';
+const localVue = createLocalVue();
+localVue.use(VueApollo);
describe('ProjectsPipelinesChartsApp', () => {
let wrapper;
- beforeEach(() => {
- wrapper = shallowMount(Component, {
- propsData: {
- counts,
- timesChartData,
- lastWeekChartData,
- lastMonthChartData,
- lastYearChartData,
+ function createMockApolloProvider() {
+ const requestHandlers = [
+ [getPipelineCountByStatus, jest.fn().mockResolvedValue(mockPipelineCount)],
+ [getProjectPipelineStatistics, jest.fn().mockResolvedValue(mockPipelineStatistics)],
+ ];
+
+ return createMockApollo(requestHandlers);
+ }
+
+ function createComponent(options = {}) {
+ const { fakeApollo } = options;
+
+ return shallowMount(Component, {
+ provide: {
+ projectPath,
},
+ localVue,
+ apolloProvider: fakeApollo,
});
+ }
+
+ beforeEach(() => {
+ const fakeApollo = createMockApolloProvider();
+ wrapper = createComponent({ fakeApollo });
});
afterEach(() => {
@@ -35,14 +51,20 @@ describe('ProjectsPipelinesChartsApp', () => {
it('displays the statistics list', () => {
const list = wrapper.find(StatisticsList);
- expect(list.exists()).toBeTruthy();
- expect(list.props('counts')).toBe(counts);
+ expect(list.exists()).toBe(true);
+ expect(list.props('counts')).toMatchObject({
+ failed: 1,
+ success: 23,
+ total: 34,
+ successRatio: 95.83333333333334,
+ totalDuration: 2471,
+ });
});
it('displays the commit duration chart', () => {
const chart = wrapper.find(GlColumnChart);
- expect(chart.exists()).toBeTruthy();
+ expect(chart.exists()).toBe(true);
expect(chart.props('yAxisTitle')).toBe('Minutes');
expect(chart.props('xAxisTitle')).toBe('Commit');
expect(chart.props('bars')).toBe(wrapper.vm.timesChartTransformedData);
@@ -52,7 +74,7 @@ describe('ProjectsPipelinesChartsApp', () => {
describe('pipelines charts', () => {
it('displays 3 area charts', () => {
- expect(wrapper.findAll(PipelinesAreaChart).length).toBe(3);
+ expect(wrapper.findAll(PipelinesAreaChart)).toHaveLength(3);
});
describe('displays individual correctly', () => {
@@ -62,7 +84,9 @@ describe('ProjectsPipelinesChartsApp', () => {
for (let i = 0; i < charts.length; i += 1) {
const chart = charts.at(i);
- expect(chart.exists()).toBeTruthy();
+ expect(chart.exists()).toBe(true);
+ // TODO: Refactor this to use the mocked data instead of the vm data
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/292085
expect(chart.props('chartData')).toBe(wrapper.vm.areaCharts[i].data);
expect(chart.text()).toBe(wrapper.vm.areaCharts[i].title);
}
diff --git a/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js b/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js
index f78608e9cb2..4e79f62ce81 100644
--- a/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js
@@ -18,7 +18,7 @@ describe('StatisticsList', () => {
wrapper = null;
});
- it('matches the snapshot', () => {
+ it('displays the counts data with labels', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
diff --git a/spec/frontend/projects/pipelines/charts/mock_data.js b/spec/frontend/projects/pipelines/charts/mock_data.js
index 84e0ccb828a..da055536fcc 100644
--- a/spec/frontend/projects/pipelines/charts/mock_data.js
+++ b/spec/frontend/projects/pipelines/charts/mock_data.js
@@ -32,3 +32,218 @@ export const transformedAreaChartData = [
data: [['01 Jan', 3], ['02 Jan', 3], ['03 Jan', 3], ['04 Jan', 3], ['05 Jan', 5]],
},
];
+
+export const mockPipelineCount = {
+ data: {
+ project: {
+ totalPipelines: { count: 34, __typename: 'PipelineConnection' },
+ successfulPipelines: { count: 23, __typename: 'PipelineConnection' },
+ failedPipelines: { count: 1, __typename: 'PipelineConnection' },
+ totalPipelineDuration: 2471,
+ __typename: 'Project',
+ },
+ },
+};
+
+export const mockPipelineStatistics = {
+ data: {
+ project: {
+ pipelineAnalytics: {
+ weekPipelinesTotals: [0, 0, 0, 0, 0, 0, 0, 0],
+ weekPipelinesLabels: [
+ '24 November',
+ '25 November',
+ '26 November',
+ '27 November',
+ '28 November',
+ '29 November',
+ '30 November',
+ '01 December',
+ ],
+ weekPipelinesSuccessful: [0, 0, 0, 0, 0, 0, 0, 0],
+ monthPipelinesLabels: [
+ '01 November',
+ '02 November',
+ '03 November',
+ '04 November',
+ '05 November',
+ '06 November',
+ '07 November',
+ '08 November',
+ '09 November',
+ '10 November',
+ '11 November',
+ '12 November',
+ '13 November',
+ '14 November',
+ '15 November',
+ '16 November',
+ '17 November',
+ '18 November',
+ '19 November',
+ '20 November',
+ '21 November',
+ '22 November',
+ '23 November',
+ '24 November',
+ '25 November',
+ '26 November',
+ '27 November',
+ '28 November',
+ '29 November',
+ '30 November',
+ '01 December',
+ ],
+ monthPipelinesTotals: [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ ],
+ monthPipelinesSuccessful: [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ ],
+ yearPipelinesLabels: [
+ 'December 2019',
+ 'January 2020',
+ 'February 2020',
+ 'March 2020',
+ 'April 2020',
+ 'May 2020',
+ 'June 2020',
+ 'July 2020',
+ 'August 2020',
+ 'September 2020',
+ 'October 2020',
+ 'November 2020',
+ 'December 2020',
+ ],
+ yearPipelinesTotals: [0, 0, 0, 0, 0, 0, 0, 0, 23, 7, 2, 2, 0],
+ yearPipelinesSuccessful: [0, 0, 0, 0, 0, 0, 0, 0, 17, 5, 1, 0, 0],
+ pipelineTimesLabels: [
+ 'b3781247',
+ 'b3781247',
+ 'a50ba059',
+ '8e414f3b',
+ 'b2964d50',
+ '7caa525b',
+ '761b164e',
+ 'd3eccd18',
+ 'e2750f63',
+ 'e2750f63',
+ '1dfb4b96',
+ 'b49d6f94',
+ '66fa2f80',
+ 'e2750f63',
+ 'fc82cf15',
+ '19fb20b2',
+ '25f03a24',
+ 'e054110f',
+ '0278b7b2',
+ '38478c16',
+ '38478c16',
+ '38478c16',
+ '1fb2103e',
+ '97b99fb5',
+ '8abc6e87',
+ 'c94e80e3',
+ '5d349a50',
+ '5d349a50',
+ '9c581037',
+ '02d95fb2',
+ ],
+ pipelineTimesValues: [
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 2,
+ 1,
+ 0,
+ 1,
+ 2,
+ 2,
+ 0,
+ 4,
+ 2,
+ 1,
+ 2,
+ 1,
+ 1,
+ 0,
+ 1,
+ 1,
+ 0,
+ 1,
+ 5,
+ 2,
+ 0,
+ 0,
+ 0,
+ ],
+ __typename: 'Analytics',
+ },
+ __typename: 'Project',
+ },
+ },
+};
diff --git a/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js b/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
new file mode 100644
index 00000000000..1fac3d07b16
--- /dev/null
+++ b/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
@@ -0,0 +1,157 @@
+import { GlAlert, GlToggle, GlTooltip } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import MockAxiosAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
+import SharedRunnersToggleComponent from '~/projects/settings/components/shared_runners_toggle.vue';
+import axios from '~/lib/utils/axios_utils';
+
+const TEST_UPDATE_PATH = '/test/update_shared_runners';
+
+jest.mock('~/flash');
+
+describe('projects/settings/components/shared_runners', () => {
+ let wrapper;
+ let mockAxios;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(SharedRunnersToggleComponent, {
+ propsData: {
+ isEnabled: false,
+ isDisabledAndUnoverridable: false,
+ isLoading: false,
+ updatePath: TEST_UPDATE_PATH,
+ ...props,
+ },
+ });
+ };
+
+ const findErrorAlert = () => wrapper.find(GlAlert);
+ const findSharedRunnersToggle = () => wrapper.find(GlToggle);
+ const findToggleTooltip = () => wrapper.find(GlTooltip);
+ const getToggleValue = () => findSharedRunnersToggle().props('value');
+ const isToggleLoading = () => findSharedRunnersToggle().props('isLoading');
+ const isToggleDisabled = () => findSharedRunnersToggle().props('disabled');
+
+ beforeEach(() => {
+ mockAxios = new MockAxiosAdapter(axios);
+ mockAxios.onPost(TEST_UPDATE_PATH).reply(200);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ mockAxios.restore();
+ });
+
+ describe('with group share settings DISABLED', () => {
+ beforeEach(() => {
+ createComponent({
+ isDisabledAndUnoverridable: true,
+ });
+ });
+
+ it('toggle should be disabled', () => {
+ expect(isToggleDisabled()).toBe(true);
+ });
+
+ it('tooltip should exist explaining why the toggle is disabled', () => {
+ expect(findToggleTooltip().exists()).toBe(true);
+ });
+ });
+
+ describe('with group share settings ENABLED', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('toggle should be enabled', () => {
+ expect(isToggleDisabled()).toBe(false);
+ });
+
+ it('loading icon, error message, and tooltip should not exist', () => {
+ expect(isToggleLoading()).toBe(false);
+ expect(findErrorAlert().exists()).toBe(false);
+ expect(findToggleTooltip().exists()).toBe(false);
+ });
+
+ describe('with shared runners DISABLED', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('toggle should be turned off', () => {
+ expect(getToggleValue()).toBe(false);
+ });
+
+ it('can enable toggle', async () => {
+ findSharedRunnersToggle().vm.$emit('change', true);
+ await waitForPromises();
+
+ expect(mockAxios.history.post[0].data).toEqual(undefined);
+ expect(mockAxios.history.post).toHaveLength(1);
+ expect(findErrorAlert().exists()).toBe(false);
+ expect(getToggleValue()).toBe(true);
+ });
+ });
+
+ describe('with shared runners ENABLED', () => {
+ beforeEach(() => {
+ createComponent({ isEnabled: true });
+ });
+
+ it('toggle should be turned on', () => {
+ expect(getToggleValue()).toBe(true);
+ });
+
+ it('can disable toggle', async () => {
+ findSharedRunnersToggle().vm.$emit('change', true);
+ await waitForPromises();
+
+ expect(mockAxios.history.post[0].data).toEqual(undefined);
+ expect(mockAxios.history.post).toHaveLength(1);
+ expect(findErrorAlert().exists()).toBe(false);
+ expect(getToggleValue()).toBe(false);
+ });
+ });
+
+ describe('loading icon', () => {
+ it('should show and hide on request', async () => {
+ createComponent();
+ expect(isToggleLoading()).toBe(false);
+
+ findSharedRunnersToggle().vm.$emit('change', true);
+ await wrapper.vm.$nextTick();
+ expect(isToggleLoading()).toBe(true);
+
+ await waitForPromises();
+ expect(isToggleLoading()).toBe(false);
+ });
+ });
+
+ describe('when request encounters an error', () => {
+ it('should show custom error message from API if it exists', async () => {
+ mockAxios.onPost(TEST_UPDATE_PATH).reply(401, { error: 'Custom API Error message' });
+ createComponent();
+ expect(getToggleValue()).toBe(false);
+
+ findSharedRunnersToggle().vm.$emit('change', true);
+ await waitForPromises();
+
+ expect(findErrorAlert().text()).toBe('Custom API Error message');
+ expect(getToggleValue()).toBe(false); // toggle value should not change
+ });
+
+ it('should show default error message if API does not return a custom error message', async () => {
+ mockAxios.onPost(TEST_UPDATE_PATH).reply(401);
+ createComponent();
+ expect(getToggleValue()).toBe(false);
+
+ findSharedRunnersToggle().vm.$emit('change', true);
+ await waitForPromises();
+
+ expect(findErrorAlert().text()).toBe('An error occurred while updating the configuration.');
+ expect(getToggleValue()).toBe(false); // toggle value should not change
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
index 5eee22f479e..7f0a4c7d3f4 100644
--- a/spec/frontend/ref/components/ref_selector_spec.js
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -128,14 +128,17 @@ describe('Ref selector component', () => {
const selectFirstBranch = () => {
findFirstBranchDropdownItem().vm.$emit('click');
+ return wrapper.vm.$nextTick();
};
const selectFirstTag = () => {
findFirstTagDropdownItem().vm.$emit('click');
+ return wrapper.vm.$nextTick();
};
const selectFirstCommit = () => {
findFirstCommitDropdownItem().vm.$emit('click');
+ return wrapper.vm.$nextTick();
};
const waitForRequests = ({ andClearMocks } = { andClearMocks: false }) =>
@@ -522,75 +525,73 @@ describe('Ref selector component', () => {
return waitForRequests();
});
- it('renders a checkmark by the selected item', () => {
+ it('renders a checkmark by the selected item', async () => {
expect(findFirstBranchDropdownItem().find(GlIcon).element).toHaveClass(
'gl-visibility-hidden',
);
- selectFirstBranch();
+ await selectFirstBranch();
- return localVue.nextTick().then(() => {
- expect(findFirstBranchDropdownItem().find(GlIcon).element).not.toHaveClass(
- 'gl-visibility-hidden',
- );
- });
+ expect(findFirstBranchDropdownItem().find(GlIcon).element).not.toHaveClass(
+ 'gl-visibility-hidden',
+ );
});
describe('when a branch is seleceted', () => {
- it("displays the branch name in the dropdown's button", () => {
+ it("displays the branch name in the dropdown's button", async () => {
expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
- selectFirstBranch();
+ await selectFirstBranch();
return localVue.nextTick().then(() => {
expect(findButtonContent().text()).toBe(fixtures.branches[0].name);
});
});
- it("updates the v-model binding with the branch's name", () => {
+ it("updates the v-model binding with the branch's name", async () => {
expect(wrapper.vm.value).toEqual('');
- selectFirstBranch();
+ await selectFirstBranch();
expect(wrapper.vm.value).toEqual(fixtures.branches[0].name);
});
});
describe('when a tag is seleceted', () => {
- it("displays the tag name in the dropdown's button", () => {
+ it("displays the tag name in the dropdown's button", async () => {
expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
- selectFirstTag();
+ await selectFirstTag();
return localVue.nextTick().then(() => {
expect(findButtonContent().text()).toBe(fixtures.tags[0].name);
});
});
- it("updates the v-model binding with the tag's name", () => {
+ it("updates the v-model binding with the tag's name", async () => {
expect(wrapper.vm.value).toEqual('');
- selectFirstTag();
+ await selectFirstTag();
expect(wrapper.vm.value).toEqual(fixtures.tags[0].name);
});
});
describe('when a commit is selected', () => {
- it("displays the full SHA in the dropdown's button", () => {
+ it("displays the full SHA in the dropdown's button", async () => {
expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
- selectFirstCommit();
+ await selectFirstCommit();
return localVue.nextTick().then(() => {
expect(findButtonContent().text()).toBe(fixtures.commit.id);
});
});
- it("updates the v-model binding with the commit's full SHA", () => {
+ it("updates the v-model binding with the commit's full SHA", async () => {
expect(wrapper.vm.value).toEqual('');
- selectFirstCommit();
+ await selectFirstCommit();
expect(wrapper.vm.value).toEqual(fixtures.commit.id);
});
diff --git a/spec/frontend/registry/explorer/components/details_page/__snapshots__/tags_loader_spec.js.snap b/spec/frontend/registry/explorer/components/details_page/__snapshots__/tags_loader_spec.js.snap
index aeb49f88770..5f191ef5561 100644
--- a/spec/frontend/registry/explorer/components/details_page/__snapshots__/tags_loader_spec.js.snap
+++ b/spec/frontend/registry/explorer/components/details_page/__snapshots__/tags_loader_spec.js.snap
@@ -2,9 +2,7 @@
exports[`TagsLoader component has the correct markup 1`] = `
<div>
- <div
- preserve-aspect-ratio="xMinYMax meet"
- >
+ <div>
<rect
height="15"
rx="4"
diff --git a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
index fc93e9094c9..ec883886026 100644
--- a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
@@ -7,9 +7,27 @@ import { DETAILS_PAGE_TITLE } from '~/registry/explorer/constants';
describe('Details Header', () => {
let wrapper;
- const mountComponent = propsData => {
+ const defaultImage = {
+ name: 'foo',
+ updatedAt: '2020-11-03T13:29:21Z',
+ project: {
+ visibility: 'public',
+ },
+ };
+
+ const findLastUpdatedAndVisibility = () => wrapper.find('[data-testid="updated-and-visibility"]');
+
+ const waitForMetadataItems = async () => {
+ // Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available
+ await wrapper.vm.$nextTick();
+ await wrapper.vm.$nextTick();
+ };
+
+ const mountComponent = (image = defaultImage) => {
wrapper = shallowMount(component, {
- propsData,
+ propsData: {
+ image,
+ },
stubs: {
GlSprintf,
TitleArea,
@@ -23,12 +41,34 @@ describe('Details Header', () => {
});
it('has the correct title ', () => {
- mountComponent();
+ mountComponent({ ...defaultImage, name: '' });
expect(wrapper.text()).toMatchInterpolatedText(DETAILS_PAGE_TITLE);
});
it('shows imageName in the title', () => {
- mountComponent({ imageName: 'foo' });
+ mountComponent();
expect(wrapper.text()).toContain('foo');
});
+
+ it('has a metadata item with last updated text', async () => {
+ mountComponent();
+ await waitForMetadataItems();
+
+ expect(findLastUpdatedAndVisibility().props('text')).toBe('Last updated 1 month ago');
+ });
+
+ describe('visibility icon', () => {
+ it('shows an eye when the project is public', async () => {
+ mountComponent();
+ await waitForMetadataItems();
+
+ expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye');
+ });
+ it('shows an eye slashed when the project is not public', async () => {
+ mountComponent({ ...defaultImage, project: { visibility: 'private' } });
+ await waitForMetadataItems();
+
+ expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye-slash');
+ });
+ });
});
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
index 3276ef911e3..e1b75636735 100644
--- a/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
@@ -15,12 +15,12 @@ import {
NOT_AVAILABLE_SIZE,
} from '~/registry/explorer/constants/index';
-import { tagsListResponse } from '../../mock_data';
+import { tagsMock } from '../../mock_data';
import { ListItem } from '../../stubs';
describe('tags list row', () => {
let wrapper;
- const [tag] = [...tagsListResponse.data];
+ const [tag] = [...tagsMock];
const defaultProps = { tag, isMobile: false, index: 0 };
@@ -65,7 +65,7 @@ describe('tags list row', () => {
});
it("does not exist when the row can't be deleted", () => {
- const customTag = { ...tag, destroy_path: '' };
+ const customTag = { ...tag, canDelete: false };
mountComponent({ ...defaultProps, tag: customTag });
@@ -137,8 +137,8 @@ describe('tags list row', () => {
mountComponent();
expect(findClipboardButton().attributes()).toMatchObject({
- text: 'location',
- title: 'location',
+ text: tag.location,
+ title: tag.location,
});
});
});
@@ -171,26 +171,26 @@ describe('tags list row', () => {
expect(findSize().exists()).toBe(true);
});
- it('contains the total_size and layers', () => {
- mountComponent({ ...defaultProps, tag: { ...tag, total_size: 1024 } });
+ it('contains the totalSize and layers', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, totalSize: 1024, layers: 10 } });
expect(findSize().text()).toMatchInterpolatedText('1.00 KiB · 10 layers');
});
- it('when total_size is missing', () => {
- mountComponent();
+ it('when totalSize is missing', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, totalSize: 0, layers: 10 } });
expect(findSize().text()).toMatchInterpolatedText(`${NOT_AVAILABLE_SIZE} · 10 layers`);
});
it('when layers are missing', () => {
- mountComponent({ ...defaultProps, tag: { ...tag, total_size: 1024, layers: null } });
+ mountComponent({ ...defaultProps, tag: { ...tag, totalSize: 1024 } });
expect(findSize().text()).toMatchInterpolatedText('1.00 KiB');
});
it('when there is 1 layer', () => {
- mountComponent({ ...defaultProps, tag: { ...tag, layers: 1 } });
+ mountComponent({ ...defaultProps, tag: { ...tag, totalSize: 0, layers: 1 } });
expect(findSize().text()).toMatchInterpolatedText(`${NOT_AVAILABLE_SIZE} · 1 layer`);
});
@@ -218,7 +218,7 @@ describe('tags list row', () => {
it('pass the correct props to time ago tooltip', () => {
mountComponent();
- expect(findTimeAgoTooltip().attributes()).toMatchObject({ time: tag.created_at });
+ expect(findTimeAgoTooltip().attributes()).toMatchObject({ time: tag.createdAt });
});
});
@@ -232,7 +232,7 @@ describe('tags list row', () => {
it('has the correct text', () => {
mountComponent();
- expect(findShortRevision().text()).toMatchInterpolatedText('Digest: 1ab51d5');
+ expect(findShortRevision().text()).toMatchInterpolatedText('Digest: 2cf3d2f');
});
it(`displays ${NOT_AVAILABLE_TEXT} when digest is missing`, () => {
@@ -260,18 +260,15 @@ describe('tags list row', () => {
});
it.each`
- destroy_path | digest
- ${'foo'} | ${null}
- ${null} | ${'foo'}
- ${null} | ${null}
- `(
- 'is disabled when destroy_path is $destroy_path and digest is $digest',
- ({ destroy_path, digest }) => {
- mountComponent({ ...defaultProps, tag: { ...tag, destroy_path, digest } });
-
- expect(findDeleteButton().attributes('disabled')).toBe('true');
- },
- );
+ canDelete | digest
+ ${true} | ${null}
+ ${false} | ${'foo'}
+ ${false} | ${null}
+ `('is disabled when canDelete is $canDelete and digest is $digest', ({ canDelete, digest }) => {
+ mountComponent({ ...defaultProps, tag: { ...tag, canDelete, digest } });
+
+ expect(findDeleteButton().attributes('disabled')).toBe('true');
+ });
it('delete event emits delete', () => {
mountComponent();
@@ -295,10 +292,10 @@ describe('tags list row', () => {
});
describe.each`
- name | finderFunction | text | icon | clipboard
- ${'published date detail'} | ${findPublishedDateDetail} | ${'Published to the bar image repository at 10:23 GMT+0000 on 2020-06-29'} | ${'clock'} | ${false}
- ${'manifest detail'} | ${findManifestDetail} | ${'Manifest digest: sha256:1ab51d519f574b636ae7788051c60239334ae8622a9fd82a0cf7bae7786dfd5c'} | ${'log'} | ${true}
- ${'configuration detail'} | ${findConfigurationDetail} | ${'Configuration digest: sha256:b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43'} | ${'cloud-gear'} | ${true}
+ name | finderFunction | text | icon | clipboard
+ ${'published date detail'} | ${findPublishedDateDetail} | ${'Published to the gitlab-org/gitlab-test/rails-12009 image repository at 01:29 GMT+0000 on 2020-11-03'} | ${'clock'} | ${false}
+ ${'manifest detail'} | ${findManifestDetail} | ${'Manifest digest: sha256:2cf3d2fdac1b04a14301d47d51cb88dcd26714c74f91440eeee99ce399089062'} | ${'log'} | ${true}
+ ${'configuration detail'} | ${findConfigurationDetail} | ${'Configuration digest: sha256:c2613843ab33aabf847965442b13a8b55a56ae28837ce182627c0716eb08c02b'} | ${'cloud-gear'} | ${true}
`('$name details row', ({ finderFunction, text, icon, clipboard }) => {
it(`has ${text} as text`, () => {
expect(finderFunction().text()).toMatchInterpolatedText(text);
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js
index ebeaa8ff870..035b59731c9 100644
--- a/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js
@@ -3,12 +3,12 @@ import { GlButton } from '@gitlab/ui';
import component from '~/registry/explorer/components/details_page/tags_list.vue';
import TagsListRow from '~/registry/explorer/components/details_page/tags_list_row.vue';
import { TAGS_LIST_TITLE, REMOVE_TAGS_BUTTON_TITLE } from '~/registry/explorer/constants/index';
-import { tagsListResponse } from '../../mock_data';
+import { tagsMock } from '../../mock_data';
describe('Tags List', () => {
let wrapper;
- const tags = [...tagsListResponse.data];
- const readOnlyTags = tags.map(t => ({ ...t, destroy_path: undefined }));
+ const tags = [...tagsMock];
+ const readOnlyTags = tags.map(t => ({ ...t, canDelete: false }));
const findTagsListRow = () => wrapper.findAll(TagsListRow);
const findDeleteButton = () => wrapper.find(GlButton);
@@ -92,7 +92,7 @@ describe('Tags List', () => {
.vm.$emit('select');
findDeleteButton().vm.$emit('click');
- expect(wrapper.emitted('delete')).toEqual([[{ centos6: true }]]);
+ expect(wrapper.emitted('delete')).toEqual([[{ 'beta-24753': true }]]);
});
});
@@ -132,7 +132,7 @@ describe('Tags List', () => {
findTagsListRow()
.at(0)
.vm.$emit('delete');
- expect(wrapper.emitted('delete')).toEqual([[{ centos6: true }]]);
+ expect(wrapper.emitted('delete')).toEqual([[{ 'beta-24753': true }]]);
});
});
});
diff --git a/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap b/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap
index a8412e2bde9..56579847468 100644
--- a/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap
+++ b/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap
@@ -1,10 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Registry Group Empty state to match the default snapshot 1`] = `
-<div
- svg-path="foo"
- title="There are no container images available in this group"
->
+<div>
<p>
With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here.
<gl-link-stub
diff --git a/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap b/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
index 8413e17c7b2..bab6b25cc15 100644
--- a/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
+++ b/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
@@ -1,10 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Registry Project Empty state to match the default snapshot 1`] = `
-<div
- svg-path="bazFoo"
- title="There are no container images stored for this project"
->
+<div>
<p>
With the Container Registry, every project can have its own space to store its Docker images.
<gl-link-stub
@@ -46,7 +43,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="gl-font-monospace!"
readonly=""
type="text"
- value="docker login bar"
+ value="bazbaz"
/>
</gl-form-input-group-stub>
@@ -67,7 +64,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="gl-font-monospace!"
readonly=""
type="text"
- value="docker build -t foo ."
+ value="foofoo"
/>
</gl-form-input-group-stub>
@@ -79,7 +76,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="gl-font-monospace!"
readonly=""
type="text"
- value="docker push foo"
+ value="barbar"
/>
</gl-form-input-group-stub>
</div>
diff --git a/spec/frontend/registry/explorer/components/list_page/cli_commands_spec.js b/spec/frontend/registry/explorer/components/list_page/cli_commands_spec.js
index 551d1eee68d..74b9ea5fd96 100644
--- a/spec/frontend/registry/explorer/components/list_page/cli_commands_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/cli_commands_spec.js
@@ -2,10 +2,8 @@ import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
import { GlDropdown } from '@gitlab/ui';
import Tracking from '~/tracking';
-import * as getters from '~/registry/explorer/stores/getters';
import QuickstartDropdown from '~/registry/explorer/components/list_page/cli_commands.vue';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
-
import {
QUICK_START,
LOGIN_COMMAND_LABEL,
@@ -14,31 +12,33 @@ import {
COPY_BUILD_TITLE,
PUSH_COMMAND_LABEL,
COPY_PUSH_TITLE,
-} from '~/registry/explorer//constants';
+} from '~/registry/explorer/constants';
+
+import { dockerCommands } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('cli_commands', () => {
let wrapper;
- let store;
+
+ const config = {
+ repositoryUrl: 'foo',
+ registryHostUrlWithPort: 'bar',
+ };
const findDropdownButton = () => wrapper.find(GlDropdown);
const findCodeInstruction = () => wrapper.findAll(CodeInstruction);
const mountComponent = () => {
- store = new Vuex.Store({
- state: {
- config: {
- repositoryUrl: 'foo',
- registryHostUrlWithPort: 'bar',
- },
- },
- getters,
- });
wrapper = mount(QuickstartDropdown, {
localVue,
- store,
+ provide() {
+ return {
+ config,
+ ...dockerCommands,
+ };
+ },
});
};
@@ -50,7 +50,6 @@ describe('cli_commands', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
- store = null;
});
it('shows the correct text on the button', () => {
@@ -67,11 +66,11 @@ describe('cli_commands', () => {
});
describe.each`
- index | labelText | titleText | getter | trackedEvent
- ${0} | ${LOGIN_COMMAND_LABEL} | ${COPY_LOGIN_TITLE} | ${'dockerLoginCommand'} | ${'click_copy_login'}
- ${1} | ${BUILD_COMMAND_LABEL} | ${COPY_BUILD_TITLE} | ${'dockerBuildCommand'} | ${'click_copy_build'}
- ${2} | ${PUSH_COMMAND_LABEL} | ${COPY_PUSH_TITLE} | ${'dockerPushCommand'} | ${'click_copy_push'}
- `('code instructions at $index', ({ index, labelText, titleText, getter, trackedEvent }) => {
+ index | labelText | titleText | command | trackedEvent
+ ${0} | ${LOGIN_COMMAND_LABEL} | ${COPY_LOGIN_TITLE} | ${dockerCommands.dockerLoginCommand} | ${'click_copy_login'}
+ ${1} | ${BUILD_COMMAND_LABEL} | ${COPY_BUILD_TITLE} | ${dockerCommands.dockerBuildCommand} | ${'click_copy_build'}
+ ${2} | ${PUSH_COMMAND_LABEL} | ${COPY_PUSH_TITLE} | ${dockerCommands.dockerPushCommand} | ${'click_copy_push'}
+ `('code instructions at $index', ({ index, labelText, titleText, command, trackedEvent }) => {
let codeInstruction;
beforeEach(() => {
@@ -85,7 +84,7 @@ describe('cli_commands', () => {
it(`has the correct props`, () => {
expect(codeInstruction.props()).toMatchObject({
label: labelText,
- instruction: store.getters[getter],
+ instruction: command,
copyText: titleText,
trackingAction: trackedEvent,
trackingLabel: 'quickstart_dropdown',
diff --git a/spec/frontend/registry/explorer/components/list_page/group_empty_state_spec.js b/spec/frontend/registry/explorer/components/list_page/group_empty_state_spec.js
index 2f51e875672..1ba2036dc34 100644
--- a/spec/frontend/registry/explorer/components/list_page/group_empty_state_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/group_empty_state_spec.js
@@ -9,24 +9,21 @@ localVue.use(Vuex);
describe('Registry Group Empty state', () => {
let wrapper;
- let store;
+ const config = {
+ noContainersImage: 'foo',
+ helpPagePath: 'baz',
+ };
beforeEach(() => {
- store = new Vuex.Store({
- state: {
- config: {
- noContainersImage: 'foo',
- helpPagePath: 'baz',
- },
- },
- });
wrapper = shallowMount(groupEmptyState, {
localVue,
- store,
stubs: {
GlEmptyState,
GlSprintf,
},
+ provide() {
+ return { config };
+ },
});
});
diff --git a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
index 9f7a2758ae1..b9839d92f1d 100644
--- a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlSprintf } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
@@ -11,13 +12,15 @@ import {
REMOVE_REPOSITORY_LABEL,
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
+ IMAGE_DELETE_SCHEDULED_STATUS,
+ IMAGE_FAILED_DELETED_STATUS,
} from '~/registry/explorer/constants';
import { RouterLink } from '../../stubs';
import { imagesListResponse } from '../../mock_data';
describe('Image List Row', () => {
let wrapper;
- const item = imagesListResponse.data[0];
+ const [item] = imagesListResponse;
const findDetailsLink = () => wrapper.find('[data-testid="details-link"]');
const findTagsCount = () => wrapper.find('[data-testid="tagsCount"]');
@@ -50,13 +53,15 @@ describe('Image List Row', () => {
describe('main tooltip', () => {
it(`the title is ${ROW_SCHEDULED_FOR_DELETION}`, () => {
mountComponent();
+
const tooltip = getBinding(wrapper.element, 'gl-tooltip');
expect(tooltip).toBeDefined();
expect(tooltip.value.title).toBe(ROW_SCHEDULED_FOR_DELETION);
});
it('is disabled when item is being deleted', () => {
- mountComponent({ item: { ...item, deleting: true } });
+ mountComponent({ item: { ...item, status: IMAGE_DELETE_SCHEDULED_STATUS } });
+
const tooltip = getBinding(wrapper.element, 'gl-tooltip');
expect(tooltip.value.disabled).toBe(false);
});
@@ -65,12 +70,13 @@ describe('Image List Row', () => {
describe('image title and path', () => {
it('contains a link to the details page', () => {
mountComponent();
+
const link = findDetailsLink();
expect(link.html()).toContain(item.path);
expect(link.props('to')).toMatchObject({
name: 'details',
params: {
- id: item.id,
+ id: getIdFromGraphQLId(item.id),
},
});
});
@@ -85,16 +91,18 @@ describe('Image List Row', () => {
describe('warning icon', () => {
it.each`
- failedDelete | cleanup_policy_started_at | shown | title
- ${true} | ${true} | ${true} | ${ASYNC_DELETE_IMAGE_ERROR_MESSAGE}
- ${false} | ${true} | ${true} | ${CLEANUP_TIMED_OUT_ERROR_MESSAGE}
- ${false} | ${false} | ${false} | ${''}
+ status | expirationPolicyStartedAt | shown | title
+ ${IMAGE_FAILED_DELETED_STATUS} | ${true} | ${true} | ${ASYNC_DELETE_IMAGE_ERROR_MESSAGE}
+ ${''} | ${true} | ${true} | ${CLEANUP_TIMED_OUT_ERROR_MESSAGE}
+ ${''} | ${false} | ${false} | ${''}
`(
- 'when failedDelete is $failedDelete and cleanup_policy_started_at is $cleanup_policy_started_at',
- ({ cleanup_policy_started_at, failedDelete, shown, title }) => {
- mountComponent({ item: { ...item, failedDelete, cleanup_policy_started_at } });
+ 'when status is $status and expirationPolicyStartedAt is $expirationPolicyStartedAt',
+ ({ expirationPolicyStartedAt, status, shown, title }) => {
+ mountComponent({ item: { ...item, status, expirationPolicyStartedAt } });
+
const icon = findWarningIcon();
expect(icon.exists()).toBe(shown);
+
if (shown) {
const tooltip = getBinding(icon.element, 'gl-tooltip');
expect(tooltip.value.title).toBe(title);
@@ -112,30 +120,33 @@ describe('Image List Row', () => {
it('has the correct props', () => {
mountComponent();
- expect(findDeleteBtn().attributes()).toMatchObject({
+
+ expect(findDeleteBtn().props()).toMatchObject({
title: REMOVE_REPOSITORY_LABEL,
- tooltipdisabled: `${Boolean(item.destroy_path)}`,
- tooltiptitle: LIST_DELETE_BUTTON_DISABLED,
+ tooltipDisabled: item.canDelete,
+ tooltipTitle: LIST_DELETE_BUTTON_DISABLED,
});
});
it('emits a delete event', () => {
mountComponent();
+
findDeleteBtn().vm.$emit('delete');
expect(wrapper.emitted('delete')).toEqual([[item]]);
});
it.each`
- destroy_path | deleting | state
- ${null} | ${null} | ${'true'}
- ${null} | ${true} | ${'true'}
- ${'foo'} | ${true} | ${'true'}
- ${'foo'} | ${false} | ${undefined}
+ canDelete | status | state
+ ${false} | ${''} | ${true}
+ ${false} | ${IMAGE_DELETE_SCHEDULED_STATUS} | ${true}
+ ${true} | ${IMAGE_DELETE_SCHEDULED_STATUS} | ${true}
+ ${true} | ${''} | ${false}
`(
- 'disabled is $state when destroy_path is $destroy_path and deleting is $deleting',
- ({ destroy_path, deleting, state }) => {
- mountComponent({ item: { ...item, destroy_path, deleting } });
- expect(findDeleteBtn().attributes('disabled')).toBe(state);
+ 'disabled is $state when canDelete is $canDelete and status is $status',
+ ({ canDelete, status, state }) => {
+ mountComponent({ item: { ...item, canDelete, status } });
+
+ expect(findDeleteBtn().props('disabled')).toBe(state);
},
);
});
@@ -155,11 +166,13 @@ describe('Image List Row', () => {
describe('tags count text', () => {
it('with one tag in the image', () => {
- mountComponent({ item: { ...item, tags_count: 1 } });
+ mountComponent({ item: { ...item, tagsCount: 1 } });
+
expect(findTagsCount().text()).toMatchInterpolatedText('1 Tag');
});
it('with more than one tag in the image', () => {
- mountComponent({ item: { ...item, tags_count: 3 } });
+ mountComponent({ item: { ...item, tagsCount: 3 } });
+
expect(findTagsCount().text()).toMatchInterpolatedText('3 Tags');
});
});
diff --git a/spec/frontend/registry/explorer/components/list_page/image_list_spec.js b/spec/frontend/registry/explorer/components/list_page/image_list_spec.js
index 03ba6ad7f80..54befc9973a 100644
--- a/spec/frontend/registry/explorer/components/list_page/image_list_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/image_list_spec.js
@@ -1,29 +1,25 @@
import { shallowMount } from '@vue/test-utils';
-import { GlPagination } from '@gitlab/ui';
+import { GlKeysetPagination } from '@gitlab/ui';
import Component from '~/registry/explorer/components/list_page/image_list.vue';
import ImageListRow from '~/registry/explorer/components/list_page/image_list_row.vue';
-import { imagesListResponse, imagePagination } from '../../mock_data';
+import { imagesListResponse, pageInfo as defaultPageInfo } from '../../mock_data';
describe('Image List', () => {
let wrapper;
const findRow = () => wrapper.findAll(ImageListRow);
- const findPagination = () => wrapper.find(GlPagination);
+ const findPagination = () => wrapper.find(GlKeysetPagination);
- const mountComponent = () => {
+ const mountComponent = (pageInfo = defaultPageInfo) => {
wrapper = shallowMount(Component, {
propsData: {
- images: imagesListResponse.data,
- pagination: imagePagination,
+ images: imagesListResponse,
+ pageInfo,
},
});
};
- beforeEach(() => {
- mountComponent();
- });
-
afterEach(() => {
wrapper.destroy();
wrapper = null;
@@ -31,10 +27,14 @@ describe('Image List', () => {
describe('list', () => {
it('contains one list element for each image', () => {
- expect(findRow().length).toBe(imagesListResponse.data.length);
+ mountComponent();
+
+ expect(findRow().length).toBe(imagesListResponse.length);
});
it('when delete event is emitted on the row it emits up a delete event', () => {
+ mountComponent();
+
findRow()
.at(0)
.vm.$emit('delete', 'foo');
@@ -44,19 +44,41 @@ describe('Image List', () => {
describe('pagination', () => {
it('exists', () => {
+ mountComponent();
+
expect(findPagination().exists()).toBe(true);
});
- it('is wired to the correct pagination props', () => {
- const pagination = findPagination();
- expect(pagination.props('perPage')).toBe(imagePagination.perPage);
- expect(pagination.props('totalItems')).toBe(imagePagination.total);
- expect(pagination.props('value')).toBe(imagePagination.page);
+ it.each`
+ hasNextPage | hasPreviousPage | isVisible
+ ${true} | ${true} | ${true}
+ ${true} | ${false} | ${true}
+ ${false} | ${true} | ${true}
+ `(
+ 'when hasNextPage is $hasNextPage and hasPreviousPage is $hasPreviousPage: is $isVisible that the component is visible',
+ ({ hasNextPage, hasPreviousPage, isVisible }) => {
+ mountComponent({ hasNextPage, hasPreviousPage });
+
+ expect(findPagination().exists()).toBe(isVisible);
+ expect(findPagination().props('hasPreviousPage')).toBe(hasPreviousPage);
+ expect(findPagination().props('hasNextPage')).toBe(hasNextPage);
+ },
+ );
+
+ it('emits "prev-page" when the user clicks the back page button', () => {
+ mountComponent({ hasPreviousPage: true });
+
+ findPagination().vm.$emit('prev');
+
+ expect(wrapper.emitted('prev-page')).toEqual([[]]);
});
- it('emits a pageChange event when the page change', () => {
- findPagination().vm.$emit(GlPagination.model.event, 2);
- expect(wrapper.emitted('pageChange')).toEqual([[2]]);
+ it('emits "next-page" when the user clicks the forward page button', () => {
+ mountComponent({ hasNextPage: true });
+
+ findPagination().vm.$emit('next');
+
+ expect(wrapper.emitted('next-page')).toEqual([[]]);
});
});
});
diff --git a/spec/frontend/registry/explorer/components/list_page/project_empty_state_spec.js b/spec/frontend/registry/explorer/components/list_page/project_empty_state_spec.js
index 73746c545cb..3a27cf1923c 100644
--- a/spec/frontend/registry/explorer/components/list_page/project_empty_state_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/project_empty_state_spec.js
@@ -3,36 +3,35 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import { GlEmptyState } from '../../stubs';
import projectEmptyState from '~/registry/explorer/components/list_page/project_empty_state.vue';
-import * as getters from '~/registry/explorer/stores/getters';
+import { dockerCommands } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Registry Project Empty state', () => {
let wrapper;
- let store;
+ const config = {
+ repositoryUrl: 'foo',
+ registryHostUrlWithPort: 'bar',
+ helpPagePath: 'baz',
+ twoFactorAuthHelpLink: 'barBaz',
+ personalAccessTokensHelpLink: 'fooBaz',
+ noContainersImage: 'bazFoo',
+ };
beforeEach(() => {
- store = new Vuex.Store({
- state: {
- config: {
- repositoryUrl: 'foo',
- registryHostUrlWithPort: 'bar',
- helpPagePath: 'baz',
- twoFactorAuthHelpLink: 'barBaz',
- personalAccessTokensHelpLink: 'fooBaz',
- noContainersImage: 'bazFoo',
- },
- },
- getters,
- });
wrapper = shallowMount(projectEmptyState, {
localVue,
- store,
stubs: {
GlEmptyState,
GlSprintf,
},
+ provide() {
+ return {
+ config,
+ ...dockerCommands,
+ };
+ },
});
});
diff --git a/spec/frontend/registry/explorer/components/registry_breadcrumb_spec.js b/spec/frontend/registry/explorer/components/registry_breadcrumb_spec.js
index d730bfcde24..fb0b98ba004 100644
--- a/spec/frontend/registry/explorer/components/registry_breadcrumb_spec.js
+++ b/spec/frontend/registry/explorer/components/registry_breadcrumb_spec.js
@@ -32,10 +32,6 @@ describe('Registry Breadcrumb', () => {
{ name: 'baz', meta: { nameGenerator } },
];
- const state = {
- imageDetails: { foo: 'bar' },
- };
-
const findDivider = () => wrapper.find('.js-divider');
const findRootRoute = () => wrapper.find({ ref: 'rootRouteLink' });
const findChildRoute = () => wrapper.find({ ref: 'childRouteLink' });
@@ -56,9 +52,6 @@ describe('Registry Breadcrumb', () => {
routes,
},
},
- $store: {
- state,
- },
},
});
};
@@ -87,7 +80,6 @@ describe('Registry Breadcrumb', () => {
});
it('the link text is calculated by nameGenerator', () => {
- expect(nameGenerator).toHaveBeenCalledWith(state);
expect(nameGenerator).toHaveBeenCalledTimes(1);
});
});
@@ -111,7 +103,6 @@ describe('Registry Breadcrumb', () => {
});
it('the link text is calculated by nameGenerator', () => {
- expect(nameGenerator).toHaveBeenCalledWith(state);
expect(nameGenerator).toHaveBeenCalledTimes(2);
});
});
diff --git a/spec/frontend/registry/explorer/mock_data.js b/spec/frontend/registry/explorer/mock_data.js
index da5f1840b5c..992d880581a 100644
--- a/spec/frontend/registry/explorer/mock_data.js
+++ b/spec/frontend/registry/explorer/mock_data.js
@@ -1,110 +1,211 @@
-export const headers = {
- 'X-PER-PAGE': 5,
- 'X-PAGE': 1,
- 'X-TOTAL': 13,
- 'X-TOTAL_PAGES': 1,
- 'X-NEXT-PAGE': null,
- 'X-PREVIOUS-PAGE': null,
-};
-export const reposServerResponse = [
+export const imagesListResponse = [
{
- destroy_path: 'path',
- id: '123',
- location: 'location',
- path: 'foo',
- tags_path: 'tags_path',
+ __typename: 'ContainerRepository',
+ id: 'gid://gitlab/ContainerRepository/26',
+ name: 'rails-12009',
+ path: 'gitlab-org/gitlab-test/rails-12009',
+ status: null,
+ location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-12009',
+ canDelete: true,
+ createdAt: '2020-11-03T13:29:21Z',
+ tagsCount: 18,
+ expirationPolicyStartedAt: null,
},
{
- destroy_path: 'path_',
- id: '456',
- location: 'location_',
- path: 'bar',
- tags_path: 'tags_path_',
+ __typename: 'ContainerRepository',
+ id: 'gid://gitlab/ContainerRepository/11',
+ name: 'rails-20572',
+ path: 'gitlab-org/gitlab-test/rails-20572',
+ status: null,
+ location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-20572',
+ canDelete: true,
+ createdAt: '2020-09-21T06:57:43Z',
+ tagsCount: 1,
+ expirationPolicyStartedAt: null,
},
];
-export const registryServerResponse = [
- {
- name: 'centos7',
- short_revision: 'b118ab5b0',
- revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
- total_size: 679,
- layers: 19,
- location: 'location',
- created_at: 1505828744434,
- destroy_path: 'path_',
+export const pageInfo = {
+ hasNextPage: true,
+ hasPreviousPage: true,
+ startCursor: 'eyJpZCI6IjI2In0',
+ endCursor: 'eyJpZCI6IjgifQ',
+ __typename: 'ContainerRepositoryConnection',
+};
+
+export const graphQLImageListMock = {
+ data: {
+ project: {
+ __typename: 'Project',
+ containerRepositoriesCount: 2,
+ containerRepositories: {
+ __typename: 'ContainerRepositoryConnection',
+ nodes: imagesListResponse,
+ pageInfo,
+ },
+ },
},
- {
- name: 'centos6',
- short_revision: 'b118ab5b0',
- revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
- total_size: 679,
- layers: 19,
- location: 'location',
- created_at: 1505828744434,
+};
+
+export const graphQLEmptyImageListMock = {
+ data: {
+ project: {
+ __typename: 'Project',
+ containerRepositoriesCount: 2,
+ containerRepositories: {
+ __typename: 'ContainerRepositoryConnection',
+ nodes: [],
+ pageInfo,
+ },
+ },
},
-];
+};
-export const imagesListResponse = {
- data: [
- {
- path: 'foo',
- location: 'location',
- destroy_path: 'path',
+export const graphQLEmptyGroupImageListMock = {
+ data: {
+ group: {
+ __typename: 'Group',
+ containerRepositoriesCount: 2,
+ containerRepositories: {
+ __typename: 'ContainerRepositoryConnection',
+ nodes: [],
+ pageInfo,
+ },
},
- {
- path: 'bar',
- location: 'location-2',
- destroy_path: 'path-2',
+ },
+};
+
+export const deletedContainerRepository = {
+ id: 'gid://gitlab/ContainerRepository/11',
+ status: 'DELETE_SCHEDULED',
+ path: 'gitlab-org/gitlab-test/rails-12009',
+ __typename: 'ContainerRepository',
+};
+
+export const graphQLImageDeleteMock = {
+ data: {
+ destroyContainerRepository: {
+ containerRepository: {
+ ...deletedContainerRepository,
+ },
+ errors: [],
+ __typename: 'DestroyContainerRepositoryPayload',
+ },
+ },
+};
+
+export const graphQLImageDeleteMockError = {
+ data: {
+ destroyContainerRepository: {
+ containerRepository: {
+ ...deletedContainerRepository,
+ },
+ errors: ['foo'],
+ __typename: 'DestroyContainerRepositoryPayload',
},
- ],
- headers,
+ },
+};
+
+export const containerRepositoryMock = {
+ id: 'gid://gitlab/ContainerRepository/26',
+ name: 'rails-12009',
+ path: 'gitlab-org/gitlab-test/rails-12009',
+ status: null,
+ location: 'host.docker.internal:5000/gitlab-org/gitlab-test/rails-12009',
+ canDelete: true,
+ createdAt: '2020-11-03T13:29:21Z',
+ updatedAt: '2020-11-03T13:29:21Z',
+ tagsCount: 13,
+ expirationPolicyStartedAt: null,
+ project: {
+ visibility: 'public',
+ __typename: 'Project',
+ },
};
-export const tagsListResponse = {
- data: [
- {
- name: 'centos6',
- revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
- short_revision: 'b118ab5b0',
- size: 19,
- layers: 10,
- location: 'location',
- path: 'bar:centos6',
- created_at: '2020-06-29T10:23:51.766+00:00',
- destroy_path: 'path',
- digest: 'sha256:1ab51d519f574b636ae7788051c60239334ae8622a9fd82a0cf7bae7786dfd5c',
+export const tagsPageInfo = {
+ __typename: 'PageInfo',
+ hasNextPage: true,
+ hasPreviousPage: true,
+ startCursor: 'MQ',
+ endCursor: 'MTA',
+};
+
+export const tagsMock = [
+ {
+ digest: 'sha256:2cf3d2fdac1b04a14301d47d51cb88dcd26714c74f91440eeee99ce399089062',
+ location: 'host.docker.internal:5000/gitlab-org/gitlab-test/rails-12009:beta-24753',
+ path: 'gitlab-org/gitlab-test/rails-12009:beta-24753',
+ name: 'beta-24753',
+ revision: 'c2613843ab33aabf847965442b13a8b55a56ae28837ce182627c0716eb08c02b',
+ shortRevision: 'c2613843a',
+ createdAt: '2020-11-03T13:29:38+00:00',
+ totalSize: 105,
+ canDelete: true,
+ __typename: 'ContainerRepositoryTag',
+ },
+ {
+ digest: 'sha256:7f94f97dff89ffd122cafe50cd32329adf682356a7a96f69cbfe313ee589791c',
+ location: 'host.docker.internal:5000/gitlab-org/gitlab-test/rails-12009:beta-31075',
+ path: 'gitlab-org/gitlab-test/rails-12009:beta-31075',
+ name: 'beta-31075',
+ revision: 'df44e7228f0f255c73e35b6f0699624a615f42746e3e8e2e4b3804a6d6fc3292',
+ shortRevision: 'df44e7228',
+ createdAt: '2020-11-03T13:29:32+00:00',
+ totalSize: 104,
+ canDelete: true,
+ __typename: 'ContainerRepositoryTag',
+ },
+];
+
+export const graphQLImageDetailsMock = override => ({
+ data: {
+ containerRepository: {
+ ...containerRepositoryMock,
+
+ tags: {
+ nodes: tagsMock,
+ pageInfo: { ...tagsPageInfo },
+ __typename: 'ContainerRepositoryTagConnection',
+ },
+ __typename: 'ContainerRepositoryDetails',
+ ...override,
},
- {
- name: 'test-tag',
- revision: 'b969de599faea2b3d9b6605a8b0897261c571acaa36db1bdc7349b5775b4e0b4',
- short_revision: 'b969de599',
- size: 19,
- layers: 10,
- path: 'foo:test-tag',
- location: 'location-2',
- created_at: '2020-06-29T10:23:51.766+00:00',
- digest: 'sha256:1ab51d519f574b636ae7788051c60239334ae8622a9fd82a0cf7bae7736dfd5c',
+ },
+});
+
+export const graphQLImageDetailsEmptyTagsMock = {
+ data: {
+ containerRepository: {
+ ...containerRepositoryMock,
+ tags: {
+ nodes: [],
+ pageInfo: {
+ __typename: 'PageInfo',
+ hasNextPage: false,
+ hasPreviousPage: false,
+ startCursor: '',
+ endCursor: '',
+ },
+ __typename: 'ContainerRepositoryTagConnection',
+ },
+ __typename: 'ContainerRepositoryDetails',
},
- ],
- headers,
+ },
};
-export const imagePagination = {
- perPage: 10,
- page: 1,
- total: 14,
- totalPages: 2,
- nextPage: 2,
+export const graphQLDeleteImageRepositoryTagsMock = {
+ data: {
+ destroyContainerRepositoryTags: {
+ deletedTagNames: [],
+ errors: [],
+ __typename: 'DestroyContainerRepositoryTagsPayload',
+ },
+ },
};
-export const imageDetailsMock = {
- id: 1,
- name: 'rails-32309',
- path: 'gitlab-org/gitlab-test/rails-32309',
- project_id: 1,
- location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-32309',
- created_at: '2020-06-29T10:23:47.838Z',
- cleanup_policy_started_at: null,
- delete_api_path: 'http://0.0.0.0:3000/api/v4/projects/1/registry/repositories/1',
+export const dockerCommands = {
+ dockerBuildCommand: 'foofoo',
+ dockerPushCommand: 'barbar',
+ dockerLoginCommand: 'bazbaz',
};
diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js
index c09b7e0c067..d307dfe590c 100644
--- a/spec/frontend/registry/explorer/pages/details_spec.js
+++ b/spec/frontend/registry/explorer/pages/details_spec.js
@@ -1,5 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlPagination } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlKeysetPagination } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import Tracking from '~/tracking';
import component from '~/registry/explorer/pages/details.vue';
import DeleteAlert from '~/registry/explorer/components/details_page/delete_alert.vue';
@@ -8,25 +11,28 @@ import DetailsHeader from '~/registry/explorer/components/details_page/details_h
import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.vue';
import TagsList from '~/registry/explorer/components/details_page/tags_list.vue';
import EmptyTagsState from '~/registry/explorer/components/details_page/empty_tags_state.vue';
-import { createStore } from '~/registry/explorer/stores/';
+
+import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.query.graphql';
+import deleteContainerRepositoryTagsMutation from '~/registry/explorer/graphql/mutations/delete_container_repository_tags.mutation.graphql';
+
import {
- SET_MAIN_LOADING,
- SET_TAGS_LIST_SUCCESS,
- SET_TAGS_PAGINATION,
- SET_INITIAL_STATE,
- SET_IMAGE_DETAILS,
-} from '~/registry/explorer/stores/mutation_types';
-
-import { tagsListResponse, imageDetailsMock } from '../mock_data';
+ graphQLImageDetailsMock,
+ graphQLImageDetailsEmptyTagsMock,
+ graphQLDeleteImageRepositoryTagsMock,
+ containerRepositoryMock,
+ tagsMock,
+ tagsPageInfo,
+} from '../mock_data';
import { DeleteModal } from '../stubs';
+const localVue = createLocalVue();
+
describe('Details Page', () => {
let wrapper;
- let dispatchSpy;
- let store;
+ let apolloProvider;
const findDeleteModal = () => wrapper.find(DeleteModal);
- const findPagination = () => wrapper.find(GlPagination);
+ const findPagination = () => wrapper.find(GlKeysetPagination);
const findTagsLoader = () => wrapper.find(TagsLoader);
const findTagsList = () => wrapper.find(TagsList);
const findDeleteAlert = () => wrapper.find(DeleteAlert);
@@ -36,15 +42,46 @@ describe('Details Page', () => {
const routeId = 1;
+ const breadCrumbState = {
+ updateName: jest.fn(),
+ };
+
+ const cleanTags = tagsMock.map(t => {
+ const result = { ...t };
+ // eslint-disable-next-line no-underscore-dangle
+ delete result.__typename;
+ return result;
+ });
+
+ const waitForApolloRequestRender = async () => {
+ await waitForPromises();
+ await wrapper.vm.$nextTick();
+ };
+
const tagsArrayToSelectedTags = tags =>
tags.reduce((acc, c) => {
acc[c.name] = true;
return acc;
}, {});
- const mountComponent = ({ options } = {}) => {
+ const mountComponent = ({
+ resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock()),
+ mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock),
+ options,
+ config = {},
+ } = {}) => {
+ localVue.use(VueApollo);
+
+ const requestHandlers = [
+ [getContainerRepositoryDetailsQuery, resolver],
+ [deleteContainerRepositoryTagsMutation, mutationResolver],
+ ];
+
+ apolloProvider = createMockApollo(requestHandlers);
+
wrapper = shallowMount(component, {
- store,
+ localVue,
+ apolloProvider,
stubs: {
DeleteModal,
},
@@ -55,17 +92,17 @@ describe('Details Page', () => {
},
},
},
+ provide() {
+ return {
+ breadCrumbState,
+ config,
+ };
+ },
...options,
});
};
beforeEach(() => {
- store = createStore();
- dispatchSpy = jest.spyOn(store, 'dispatch');
- dispatchSpy.mockResolvedValue();
- store.commit(SET_TAGS_LIST_SUCCESS, tagsListResponse.data);
- store.commit(SET_TAGS_PAGINATION, tagsListResponse.headers);
- store.commit(SET_IMAGE_DETAILS, imageDetailsMock);
jest.spyOn(Tracking, 'event');
});
@@ -74,85 +111,90 @@ describe('Details Page', () => {
wrapper = null;
});
- describe('lifecycle events', () => {
- it('calls the appropriate action on mount', () => {
- mountComponent();
- expect(dispatchSpy).toHaveBeenCalledWith('requestImageDetailsAndTagsList', routeId);
- });
- });
-
describe('when isLoading is true', () => {
- beforeEach(() => {
- store.commit(SET_MAIN_LOADING, true);
+ it('shows the loader', () => {
mountComponent();
- });
-
- afterEach(() => store.commit(SET_MAIN_LOADING, false));
- it('shows the loader', () => {
expect(findTagsLoader().exists()).toBe(true);
});
it('does not show the list', () => {
+ mountComponent();
+
expect(findTagsList().exists()).toBe(false);
});
it('does not show pagination', () => {
+ mountComponent();
+
expect(findPagination().exists()).toBe(false);
});
});
describe('when the list of tags is empty', () => {
- beforeEach(() => {
- store.commit(SET_TAGS_LIST_SUCCESS, []);
- mountComponent();
- });
+ const resolver = jest.fn().mockResolvedValue(graphQLImageDetailsEmptyTagsMock);
+
+ it('has the empty state', async () => {
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
- it('has the empty state', () => {
expect(findEmptyTagsState().exists()).toBe(true);
});
- it('does not show the loader', () => {
+ it('does not show the loader', async () => {
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
expect(findTagsLoader().exists()).toBe(false);
});
- it('does not show the list', () => {
+ it('does not show the list', async () => {
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
expect(findTagsList().exists()).toBe(false);
});
});
describe('list', () => {
- beforeEach(() => {
+ it('exists', async () => {
mountComponent();
- });
- it('exists', () => {
+ await waitForApolloRequestRender();
+
expect(findTagsList().exists()).toBe(true);
});
- it('has the correct props bound', () => {
+ it('has the correct props bound', async () => {
+ mountComponent();
+
+ await waitForApolloRequestRender();
+
expect(findTagsList().props()).toMatchObject({
isMobile: false,
- tags: store.state.tags,
+ tags: cleanTags,
});
});
describe('deleteEvent', () => {
describe('single item', () => {
let tagToBeDeleted;
- beforeEach(() => {
- [tagToBeDeleted] = store.state.tags;
+ beforeEach(async () => {
+ mountComponent();
+
+ await waitForApolloRequestRender();
+
+ [tagToBeDeleted] = cleanTags;
findTagsList().vm.$emit('delete', { [tagToBeDeleted.name]: true });
});
- it('open the modal', () => {
+ it('open the modal', async () => {
expect(DeleteModal.methods.show).toHaveBeenCalled();
});
- it('maps the selection to itemToBeDeleted', () => {
- expect(wrapper.vm.itemsToBeDeleted).toEqual([tagToBeDeleted]);
- });
-
it('tracks a single delete event', () => {
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'registry_tag_delete',
@@ -161,18 +203,18 @@ describe('Details Page', () => {
});
describe('multiple items', () => {
- beforeEach(() => {
- findTagsList().vm.$emit('delete', tagsArrayToSelectedTags(store.state.tags));
+ beforeEach(async () => {
+ mountComponent();
+
+ await waitForApolloRequestRender();
+
+ findTagsList().vm.$emit('delete', tagsArrayToSelectedTags(cleanTags));
});
it('open the modal', () => {
expect(DeleteModal.methods.show).toHaveBeenCalled();
});
- it('maps the selection to itemToBeDeleted', () => {
- expect(wrapper.vm.itemsToBeDeleted).toEqual(store.state.tags);
- });
-
it('tracks a single delete event', () => {
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'bulk_registry_tag_delete',
@@ -183,40 +225,77 @@ describe('Details Page', () => {
});
describe('pagination', () => {
- beforeEach(() => {
+ it('exists', async () => {
mountComponent();
- });
- it('exists', () => {
+ await waitForApolloRequestRender();
+
expect(findPagination().exists()).toBe(true);
});
- it('is wired to the correct pagination props', () => {
- const pagination = findPagination();
- expect(pagination.props('perPage')).toBe(store.state.tagsPagination.perPage);
- expect(pagination.props('totalItems')).toBe(store.state.tagsPagination.total);
- expect(pagination.props('value')).toBe(store.state.tagsPagination.page);
+ it('is hidden when there are no more pages', async () => {
+ mountComponent({ resolver: jest.fn().mockResolvedValue(graphQLImageDetailsEmptyTagsMock) });
+
+ await waitForApolloRequestRender();
+
+ expect(findPagination().exists()).toBe(false);
});
- it('fetch the data from the API when the v-model changes', () => {
- dispatchSpy.mockResolvedValue();
- findPagination().vm.$emit(GlPagination.model.event, 2);
- expect(store.dispatch).toHaveBeenCalledWith('requestTagsList', {
- page: 2,
+ it('is wired to the correct pagination props', async () => {
+ mountComponent();
+
+ await waitForApolloRequestRender();
+
+ expect(findPagination().props()).toMatchObject({
+ hasNextPage: tagsPageInfo.hasNextPage,
+ hasPreviousPage: tagsPageInfo.hasPreviousPage,
});
});
+
+ it('fetch next page when user clicks next', async () => {
+ const resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock());
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
+ findPagination().vm.$emit('next');
+
+ expect(resolver).toHaveBeenCalledWith(
+ expect.objectContaining({ after: tagsPageInfo.endCursor }),
+ );
+ });
+
+ it('fetch previous page when user clicks prev', async () => {
+ const resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock());
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
+ findPagination().vm.$emit('prev');
+
+ expect(resolver).toHaveBeenCalledWith(
+ expect.objectContaining({ first: null, before: tagsPageInfo.startCursor }),
+ );
+ });
});
describe('modal', () => {
- it('exists', () => {
+ it('exists', async () => {
mountComponent();
+
+ await waitForApolloRequestRender();
+
expect(findDeleteModal().exists()).toBe(true);
});
describe('cancel event', () => {
- it('tracks cancel_delete', () => {
+ it('tracks cancel_delete', async () => {
mountComponent();
+
+ await waitForApolloRequestRender();
+
findDeleteModal().vm.$emit('cancel');
+
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'cancel_delete', {
label: 'registry_tag_delete',
});
@@ -224,45 +303,62 @@ describe('Details Page', () => {
});
describe('confirmDelete event', () => {
+ let mutationResolver;
+
+ beforeEach(() => {
+ mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock);
+ mountComponent({ mutationResolver });
+
+ return waitForApolloRequestRender();
+ });
describe('when one item is selected to be deleted', () => {
- beforeEach(() => {
- mountComponent();
- findTagsList().vm.$emit('delete', { [store.state.tags[0].name]: true });
- });
+ it('calls apollo mutation with the right parameters', async () => {
+ findTagsList().vm.$emit('delete', { [cleanTags[0].name]: true });
+
+ await wrapper.vm.$nextTick();
- it('dispatch requestDeleteTag with the right parameters', () => {
findDeleteModal().vm.$emit('confirmDelete');
- expect(dispatchSpy).toHaveBeenCalledWith('requestDeleteTag', {
- tag: store.state.tags[0],
- });
+
+ expect(mutationResolver).toHaveBeenCalledWith(
+ expect.objectContaining({ tagNames: [cleanTags[0].name] }),
+ );
});
});
describe('when more than one item is selected to be deleted', () => {
- beforeEach(() => {
- mountComponent();
- findTagsList().vm.$emit('delete', tagsArrayToSelectedTags(store.state.tags));
- });
+ it('calls apollo mutation with the right parameters', async () => {
+ findTagsList().vm.$emit('delete', { ...tagsArrayToSelectedTags(tagsMock) });
+
+ await wrapper.vm.$nextTick();
- it('dispatch requestDeleteTags with the right parameters', () => {
findDeleteModal().vm.$emit('confirmDelete');
- expect(dispatchSpy).toHaveBeenCalledWith('requestDeleteTags', {
- ids: store.state.tags.map(t => t.name),
- });
+
+ expect(mutationResolver).toHaveBeenCalledWith(
+ expect.objectContaining({ tagNames: tagsMock.map(t => t.name) }),
+ );
});
});
});
});
describe('Header', () => {
- it('exists', () => {
+ it('exists', async () => {
mountComponent();
+
+ await waitForApolloRequestRender();
expect(findDetailsHeader().exists()).toBe(true);
});
- it('has the correct props', () => {
+ it('has the correct props', async () => {
mountComponent();
- expect(findDetailsHeader().props()).toEqual({ imageName: imageDetailsMock.name });
+
+ await waitForApolloRequestRender();
+ expect(findDetailsHeader().props('image')).toMatchObject({
+ name: containerRepositoryMock.name,
+ project: {
+ visibility: containerRepositoryMock.project.visibility,
+ },
+ });
});
});
@@ -273,20 +369,25 @@ describe('Details Page', () => {
};
const deleteAlertType = 'success_tag';
- it('exists', () => {
+ it('exists', async () => {
mountComponent();
+
+ await waitForApolloRequestRender();
expect(findDeleteAlert().exists()).toBe(true);
});
- it('has the correct props', () => {
- store.commit(SET_INITIAL_STATE, { ...config });
+ it('has the correct props', async () => {
mountComponent({
options: {
data: () => ({
deleteAlertType,
}),
},
+ config,
});
+
+ await waitForApolloRequestRender();
+
expect(findDeleteAlert().props()).toEqual({ ...config, deleteAlertType });
});
});
@@ -298,30 +399,38 @@ describe('Details Page', () => {
};
describe('when expiration_policy_started is not null', () => {
+ let resolver;
+
beforeEach(() => {
- store.commit(SET_IMAGE_DETAILS, {
- ...imageDetailsMock,
- cleanup_policy_started_at: Date.now().toString(),
- });
+ resolver = jest.fn().mockResolvedValue(
+ graphQLImageDetailsMock({
+ expirationPolicyStartedAt: Date.now().toString(),
+ }),
+ );
});
- it('exists', () => {
- mountComponent();
+ it('exists', async () => {
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
expect(findPartialCleanupAlert().exists()).toBe(true);
});
- it('has the correct props', () => {
- store.commit(SET_INITIAL_STATE, { ...config });
+ it('has the correct props', async () => {
+ mountComponent({ resolver, config });
- mountComponent();
+ await waitForApolloRequestRender();
expect(findPartialCleanupAlert().props()).toEqual({ ...config });
});
it('dismiss hides the component', async () => {
- mountComponent();
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
expect(findPartialCleanupAlert().exists()).toBe(true);
+
findPartialCleanupAlert().vm.$emit('dismiss');
await wrapper.vm.$nextTick();
@@ -331,11 +440,22 @@ describe('Details Page', () => {
});
describe('when expiration_policy_started is null', () => {
- it('the component is hidden', () => {
+ it('the component is hidden', async () => {
mountComponent();
+ await waitForApolloRequestRender();
expect(findPartialCleanupAlert().exists()).toBe(false);
});
});
});
+
+ describe('Breadcrumb connection', () => {
+ it('when the details are fetched updates the name', async () => {
+ mountComponent();
+
+ await waitForApolloRequestRender();
+
+ expect(breadCrumbState.updateName).toHaveBeenCalledWith(containerRepositoryMock.name);
+ });
+ });
});
diff --git a/spec/frontend/registry/explorer/pages/index_spec.js b/spec/frontend/registry/explorer/pages/index_spec.js
index 1dc5376cacf..b5f718b3e61 100644
--- a/spec/frontend/registry/explorer/pages/index_spec.js
+++ b/spec/frontend/registry/explorer/pages/index_spec.js
@@ -1,16 +1,13 @@
import { shallowMount } from '@vue/test-utils';
import component from '~/registry/explorer/pages/index.vue';
-import { createStore } from '~/registry/explorer/stores/';
describe('List Page', () => {
let wrapper;
- let store;
const findRouterView = () => wrapper.find({ ref: 'router-view' });
const mountComponent = () => {
wrapper = shallowMount(component, {
- store,
stubs: {
RouterView: true,
},
@@ -18,7 +15,6 @@ describe('List Page', () => {
};
beforeEach(() => {
- store = createStore();
mountComponent();
});
diff --git a/spec/frontend/registry/explorer/pages/list_spec.js b/spec/frontend/registry/explorer/pages/list_spec.js
index b24422adb03..7d32a667011 100644
--- a/spec/frontend/registry/explorer/pages/list_spec.js
+++ b/spec/frontend/registry/explorer/pages/list_spec.js
@@ -1,5 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
import { GlSkeletonLoader, GlSprintf, GlAlert, GlSearchBoxByClick } from '@gitlab/ui';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Tracking from '~/tracking';
import component from '~/registry/explorer/pages/list.vue';
@@ -9,27 +11,35 @@ import ProjectEmptyState from '~/registry/explorer/components/list_page/project_
import RegistryHeader from '~/registry/explorer/components/list_page/registry_header.vue';
import ImageList from '~/registry/explorer/components/list_page/image_list.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
-import { createStore } from '~/registry/explorer/stores/';
-import {
- SET_MAIN_LOADING,
- SET_IMAGES_LIST_SUCCESS,
- SET_PAGINATION,
- SET_INITIAL_STATE,
-} from '~/registry/explorer/stores/mutation_types';
+
import {
DELETE_IMAGE_SUCCESS_MESSAGE,
DELETE_IMAGE_ERROR_MESSAGE,
IMAGE_REPOSITORY_LIST_LABEL,
SEARCH_PLACEHOLDER_TEXT,
} from '~/registry/explorer/constants';
-import { imagesListResponse } from '../mock_data';
+
+import getProjectContainerRepositoriesQuery from '~/registry/explorer/graphql/queries/get_project_container_repositories.query.graphql';
+import getGroupContainerRepositoriesQuery from '~/registry/explorer/graphql/queries/get_group_container_repositories.query.graphql';
+import deleteContainerRepositoryMutation from '~/registry/explorer/graphql/mutations/delete_container_repository.mutation.graphql';
+
+import {
+ graphQLImageListMock,
+ graphQLImageDeleteMock,
+ deletedContainerRepository,
+ graphQLImageDeleteMockError,
+ graphQLEmptyImageListMock,
+ graphQLEmptyGroupImageListMock,
+ pageInfo,
+} from '../mock_data';
import { GlModal, GlEmptyState } from '../stubs';
import { $toast } from '../../shared/mocks';
+const localVue = createLocalVue();
+
describe('List Page', () => {
let wrapper;
- let dispatchSpy;
- let store;
+ let apolloProvider;
const findDeleteModal = () => wrapper.find(GlModal);
const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader);
@@ -47,9 +57,31 @@ describe('List Page', () => {
const findSearchBox = () => wrapper.find(GlSearchBoxByClick);
const findEmptySearchMessage = () => wrapper.find('[data-testid="emptySearch"]');
- const mountComponent = ({ mocks } = {}) => {
+ const waitForApolloRequestRender = async () => {
+ await waitForPromises();
+ await wrapper.vm.$nextTick();
+ };
+
+ const mountComponent = ({
+ mocks,
+ resolver = jest.fn().mockResolvedValue(graphQLImageListMock),
+ groupResolver = jest.fn().mockResolvedValue(graphQLImageListMock),
+ mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMock),
+ config = {},
+ } = {}) => {
+ localVue.use(VueApollo);
+
+ const requestHandlers = [
+ [getProjectContainerRepositoriesQuery, resolver],
+ [getGroupContainerRepositoriesQuery, groupResolver],
+ [deleteContainerRepositoryMutation, mutationResolver],
+ ];
+
+ apolloProvider = createMockApollo(requestHandlers);
+
wrapper = shallowMount(component, {
- store,
+ localVue,
+ apolloProvider,
stubs: {
GlModal,
GlEmptyState,
@@ -64,42 +96,27 @@ describe('List Page', () => {
},
...mocks,
},
+ provide() {
+ return {
+ config,
+ };
+ },
});
};
- beforeEach(() => {
- store = createStore();
- dispatchSpy = jest.spyOn(store, 'dispatch');
- dispatchSpy.mockResolvedValue();
- store.commit(SET_IMAGES_LIST_SUCCESS, imagesListResponse.data);
- store.commit(SET_PAGINATION, imagesListResponse.headers);
- });
-
afterEach(() => {
wrapper.destroy();
});
- describe('API calls', () => {
- it.each`
- imageList | name | called
- ${[]} | ${'foo'} | ${['requestImagesList']}
- ${imagesListResponse.data} | ${undefined} | ${['requestImagesList']}
- ${imagesListResponse.data} | ${'foo'} | ${undefined}
- `(
- 'with images equal $imageList and name $name dispatch calls $called',
- ({ imageList, name, called }) => {
- store.commit(SET_IMAGES_LIST_SUCCESS, imageList);
- dispatchSpy.mockClear();
- mountComponent({ mocks: { $route: { name } } });
-
- expect(dispatchSpy.mock.calls[0]).toEqual(called);
- },
- );
- });
-
- it('contains registry header', () => {
+ it('contains registry header', async () => {
mountComponent();
+
+ await waitForApolloRequestRender();
+
expect(findRegistryHeader().exists()).toBe(true);
+ expect(findRegistryHeader().props()).toMatchObject({
+ imagesCount: 2,
+ });
});
describe('connection error', () => {
@@ -109,88 +126,100 @@ describe('List Page', () => {
helpPagePath: 'bar',
};
- beforeEach(() => {
- store.commit(SET_INITIAL_STATE, config);
- mountComponent();
- });
-
- afterEach(() => {
- store.commit(SET_INITIAL_STATE, {});
- });
-
it('should show an empty state', () => {
+ mountComponent({ config });
+
expect(findEmptyState().exists()).toBe(true);
});
it('empty state should have an svg-path', () => {
- expect(findEmptyState().attributes('svg-path')).toBe(config.containersErrorImage);
+ mountComponent({ config });
+
+ expect(findEmptyState().props('svgPath')).toBe(config.containersErrorImage);
});
it('empty state should have a description', () => {
- expect(findEmptyState().html()).toContain('connection error');
+ mountComponent({ config });
+
+ expect(findEmptyState().props('title')).toContain('connection error');
});
it('should not show the loading or default state', () => {
+ mountComponent({ config });
+
expect(findSkeletonLoader().exists()).toBe(false);
expect(findImageList().exists()).toBe(false);
});
});
describe('isLoading is true', () => {
- beforeEach(() => {
- store.commit(SET_MAIN_LOADING, true);
+ it('shows the skeleton loader', () => {
mountComponent();
- });
-
- afterEach(() => store.commit(SET_MAIN_LOADING, false));
- it('shows the skeleton loader', () => {
expect(findSkeletonLoader().exists()).toBe(true);
});
it('imagesList is not visible', () => {
+ mountComponent();
+
expect(findImageList().exists()).toBe(false);
});
it('cli commands is not visible', () => {
+ mountComponent();
+
expect(findCliCommands().exists()).toBe(false);
});
});
describe('list is empty', () => {
- beforeEach(() => {
- store.commit(SET_IMAGES_LIST_SUCCESS, []);
- mountComponent();
- return waitForPromises();
- });
+ describe('project page', () => {
+ const resolver = jest.fn().mockResolvedValue(graphQLEmptyImageListMock);
- it('cli commands is not visible', () => {
- expect(findCliCommands().exists()).toBe(false);
- });
+ it('cli commands is not visible', async () => {
+ mountComponent({ resolver });
- it('project empty state is visible', () => {
- expect(findProjectEmptyState().exists()).toBe(true);
- });
+ await waitForApolloRequestRender();
- describe('is group page is true', () => {
- beforeEach(() => {
- store.commit(SET_INITIAL_STATE, { isGroupPage: true });
- mountComponent();
+ expect(findCliCommands().exists()).toBe(false);
});
- afterEach(() => {
- store.commit(SET_INITIAL_STATE, { isGroupPage: undefined });
+ it('project empty state is visible', async () => {
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
+ expect(findProjectEmptyState().exists()).toBe(true);
});
+ });
+ describe('group page', () => {
+ const groupResolver = jest.fn().mockResolvedValue(graphQLEmptyGroupImageListMock);
+
+ const config = {
+ isGroupPage: true,
+ };
+
+ it('group empty state is visible', async () => {
+ mountComponent({ groupResolver, config });
+
+ await waitForApolloRequestRender();
- it('group empty state is visible', () => {
expect(findGroupEmptyState().exists()).toBe(true);
});
- it('cli commands is not visible', () => {
+ it('cli commands is not visible', async () => {
+ mountComponent({ groupResolver, config });
+
+ await waitForApolloRequestRender();
+
expect(findCliCommands().exists()).toBe(false);
});
- it('list header is not visible', () => {
+ it('list header is not visible', async () => {
+ mountComponent({ groupResolver, config });
+
+ await waitForApolloRequestRender();
+
expect(findListHeader().exists()).toBe(false);
});
});
@@ -198,55 +227,91 @@ describe('List Page', () => {
describe('list is not empty', () => {
describe('unfiltered state', () => {
- beforeEach(() => {
+ it('quick start is visible', async () => {
mountComponent();
- });
- it('quick start is visible', () => {
+ await waitForApolloRequestRender();
+
expect(findCliCommands().exists()).toBe(true);
});
- it('list component is visible', () => {
+ it('list component is visible', async () => {
+ mountComponent();
+
+ await waitForApolloRequestRender();
+
expect(findImageList().exists()).toBe(true);
});
- it('list header is visible', () => {
+ it('list header is visible', async () => {
+ mountComponent();
+
+ await waitForApolloRequestRender();
+
const header = findListHeader();
expect(header.exists()).toBe(true);
expect(header.text()).toBe(IMAGE_REPOSITORY_LIST_LABEL);
});
describe('delete image', () => {
- const itemToDelete = { path: 'bar' };
- it('should call deleteItem when confirming deletion', () => {
- dispatchSpy.mockResolvedValue();
- findImageList().vm.$emit('delete', itemToDelete);
- expect(wrapper.vm.itemToDelete).toEqual(itemToDelete);
+ const deleteImage = async () => {
+ await wrapper.vm.$nextTick();
+
+ findImageList().vm.$emit('delete', deletedContainerRepository);
findDeleteModal().vm.$emit('ok');
- expect(store.dispatch).toHaveBeenCalledWith(
- 'requestDeleteImage',
- wrapper.vm.itemToDelete,
+
+ await waitForApolloRequestRender();
+ };
+
+ it('should call deleteItem when confirming deletion', async () => {
+ const mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMock);
+ mountComponent({ mutationResolver });
+
+ await deleteImage();
+
+ expect(wrapper.vm.itemToDelete).toEqual(deletedContainerRepository);
+ expect(mutationResolver).toHaveBeenCalledWith({ id: deletedContainerRepository.id });
+
+ const updatedImage = findImageList()
+ .props('images')
+ .find(i => i.id === deletedContainerRepository.id);
+
+ expect(updatedImage.status).toBe(deletedContainerRepository.status);
+ });
+
+ it('should show a success alert when delete request is successful', async () => {
+ const mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMock);
+ mountComponent({ mutationResolver });
+
+ await deleteImage();
+
+ const alert = findDeleteAlert();
+ expect(alert.exists()).toBe(true);
+ expect(alert.text().replace(/\s\s+/gm, ' ')).toBe(
+ DELETE_IMAGE_SUCCESS_MESSAGE.replace('%{title}', wrapper.vm.itemToDelete.path),
);
});
- it('should show a success alert when delete request is successful', () => {
- dispatchSpy.mockResolvedValue();
- findImageList().vm.$emit('delete', itemToDelete);
- expect(wrapper.vm.itemToDelete).toEqual(itemToDelete);
- return wrapper.vm.handleDeleteImage().then(() => {
+ describe('when delete request fails it shows an alert', () => {
+ it('user recoverable error', async () => {
+ const mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMockError);
+ mountComponent({ mutationResolver });
+
+ await deleteImage();
+
const alert = findDeleteAlert();
expect(alert.exists()).toBe(true);
expect(alert.text().replace(/\s\s+/gm, ' ')).toBe(
- DELETE_IMAGE_SUCCESS_MESSAGE.replace('%{title}', wrapper.vm.itemToDelete.path),
+ DELETE_IMAGE_ERROR_MESSAGE.replace('%{title}', wrapper.vm.itemToDelete.path),
);
});
- });
- it('should show an error alert when delete request fails', () => {
- dispatchSpy.mockRejectedValue();
- findImageList().vm.$emit('delete', itemToDelete);
- expect(wrapper.vm.itemToDelete).toEqual(itemToDelete);
- return wrapper.vm.handleDeleteImage().then(() => {
+ it('network error', async () => {
+ const mutationResolver = jest.fn().mockRejectedValue();
+ mountComponent({ mutationResolver });
+
+ await deleteImage();
+
const alert = findDeleteAlert();
expect(alert.exists()).toBe(true);
expect(alert.text().replace(/\s\s+/gm, ' ')).toBe(
@@ -258,38 +323,68 @@ describe('List Page', () => {
});
describe('search', () => {
- it('has a search box element', () => {
+ const doSearch = async () => {
+ await waitForApolloRequestRender();
+ findSearchBox().vm.$emit('submit', 'centos6');
+ await wrapper.vm.$nextTick();
+ };
+
+ it('has a search box element', async () => {
mountComponent();
+
+ await waitForApolloRequestRender();
+
const searchBox = findSearchBox();
expect(searchBox.exists()).toBe(true);
expect(searchBox.attributes('placeholder')).toBe(SEARCH_PLACEHOLDER_TEXT);
});
- it('performs a search', () => {
- mountComponent();
- findSearchBox().vm.$emit('submit', 'foo');
- expect(store.dispatch).toHaveBeenCalledWith('requestImagesList', {
- name: 'foo',
- });
+ it('performs a search', async () => {
+ const resolver = jest.fn().mockResolvedValue(graphQLImageListMock);
+ mountComponent({ resolver });
+
+ await doSearch();
+
+ expect(resolver).toHaveBeenCalledWith(expect.objectContaining({ name: 'centos6' }));
});
- it('when search result is empty displays an empty search message', () => {
- mountComponent();
- store.commit(SET_IMAGES_LIST_SUCCESS, []);
- return wrapper.vm.$nextTick().then(() => {
- expect(findEmptySearchMessage().exists()).toBe(true);
- });
+ it('when search result is empty displays an empty search message', async () => {
+ const resolver = jest.fn().mockResolvedValue(graphQLImageListMock);
+ mountComponent({ resolver });
+
+ resolver.mockResolvedValue(graphQLEmptyImageListMock);
+
+ await doSearch();
+
+ expect(findEmptySearchMessage().exists()).toBe(true);
});
});
describe('pagination', () => {
- it('pageChange event triggers the appropriate store function', () => {
- mountComponent();
- findImageList().vm.$emit('pageChange', 2);
- expect(store.dispatch).toHaveBeenCalledWith('requestImagesList', {
- pagination: { page: 2 },
- name: wrapper.vm.search,
- });
+ it('prev-page event triggers a fetchMore request', async () => {
+ const resolver = jest.fn().mockResolvedValue(graphQLImageListMock);
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
+ findImageList().vm.$emit('prev-page');
+
+ expect(resolver).toHaveBeenCalledWith(
+ expect.objectContaining({ first: null, before: pageInfo.startCursor }),
+ );
+ });
+
+ it('next-page event triggers a fetchMore request', async () => {
+ const resolver = jest.fn().mockResolvedValue(graphQLImageListMock);
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
+ findImageList().vm.$emit('next-page');
+
+ expect(resolver).toHaveBeenCalledWith(
+ expect.objectContaining({ after: pageInfo.endCursor }),
+ );
});
});
});
@@ -324,11 +419,11 @@ describe('List Page', () => {
beforeEach(() => {
jest.spyOn(Tracking, 'event');
- dispatchSpy.mockResolvedValue();
});
it('send an event when delete button is clicked', () => {
findImageList().vm.$emit('delete', {});
+
testTrackingCall('click_button');
});
diff --git a/spec/frontend/registry/explorer/stores/actions_spec.js b/spec/frontend/registry/explorer/stores/actions_spec.js
deleted file mode 100644
index dcd4d8015a4..00000000000
--- a/spec/frontend/registry/explorer/stores/actions_spec.js
+++ /dev/null
@@ -1,362 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
-import { TEST_HOST } from 'helpers/test_constants';
-import createFlash from '~/flash';
-import Api from '~/api';
-import axios from '~/lib/utils/axios_utils';
-import * as actions from '~/registry/explorer/stores/actions';
-import * as types from '~/registry/explorer/stores/mutation_types';
-import { reposServerResponse, registryServerResponse } from '../mock_data';
-import * as utils from '~/registry/explorer/utils';
-import {
- FETCH_IMAGES_LIST_ERROR_MESSAGE,
- FETCH_TAGS_LIST_ERROR_MESSAGE,
- FETCH_IMAGE_DETAILS_ERROR_MESSAGE,
-} from '~/registry/explorer/constants/index';
-
-jest.mock('~/flash.js');
-jest.mock('~/registry/explorer/utils');
-
-describe('Actions RegistryExplorer Store', () => {
- let mock;
- const endpoint = `${TEST_HOST}/endpoint.json`;
-
- const url = `${endpoint}/1}`;
- jest.spyOn(utils, 'pathGenerator').mockReturnValue(url);
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('sets initial state', done => {
- const initialState = {
- config: {
- endpoint,
- },
- };
-
- testAction(
- actions.setInitialState,
- initialState,
- null,
- [{ type: types.SET_INITIAL_STATE, payload: initialState }],
- [],
- done,
- );
- });
-
- it('setShowGarbageCollectionTip', done => {
- testAction(
- actions.setShowGarbageCollectionTip,
- true,
- null,
- [{ type: types.SET_SHOW_GARBAGE_COLLECTION_TIP, payload: true }],
- [],
- done,
- );
- });
-
- describe('receives api responses', () => {
- const response = {
- data: [1, 2, 3],
- headers: {
- page: 1,
- perPage: 10,
- },
- };
-
- it('images list response', done => {
- testAction(
- actions.receiveImagesListSuccess,
- response,
- null,
- [
- { type: types.SET_IMAGES_LIST_SUCCESS, payload: response.data },
- { type: types.SET_PAGINATION, payload: response.headers },
- ],
- [],
- done,
- );
- });
-
- it('tags list response', done => {
- testAction(
- actions.receiveTagsListSuccess,
- response,
- null,
- [
- { type: types.SET_TAGS_LIST_SUCCESS, payload: response.data },
- { type: types.SET_TAGS_PAGINATION, payload: response.headers },
- ],
- [],
- done,
- );
- });
- });
-
- describe('fetch images list', () => {
- it('sets the imagesList and pagination', done => {
- mock.onGet(endpoint).replyOnce(200, reposServerResponse, {});
-
- testAction(
- actions.requestImagesList,
- {},
- {
- config: {
- endpoint,
- },
- },
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [{ type: 'receiveImagesListSuccess', payload: { data: reposServerResponse, headers: {} } }],
- done,
- );
- });
-
- it('should create flash on error', done => {
- testAction(
- actions.requestImagesList,
- {},
- {
- config: {
- endpoint: null,
- },
- },
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [],
- () => {
- expect(createFlash).toHaveBeenCalledWith({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
- done();
- },
- );
- });
- });
-
- describe('fetch tags list', () => {
- it('sets the tagsList', done => {
- mock.onGet(url).replyOnce(200, registryServerResponse, {});
-
- testAction(
- actions.requestTagsList,
- {},
- {},
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [
- {
- type: 'receiveTagsListSuccess',
- payload: { data: registryServerResponse, headers: {} },
- },
- ],
- done,
- );
- });
-
- it('should create flash on error', done => {
- testAction(
- actions.requestTagsList,
- {},
- {},
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [],
- () => {
- expect(createFlash).toHaveBeenCalledWith({ message: FETCH_TAGS_LIST_ERROR_MESSAGE });
- done();
- },
- );
- });
- });
-
- describe('request delete single tag', () => {
- it('successfully performs the delete request', done => {
- const deletePath = 'delete/path';
- mock.onDelete(deletePath).replyOnce(200);
-
- testAction(
- actions.requestDeleteTag,
- {
- tag: {
- destroy_path: deletePath,
- },
- },
- {
- tagsPagination: {},
- },
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [
- {
- type: 'setShowGarbageCollectionTip',
- payload: true,
- },
- {
- type: 'requestTagsList',
- payload: {},
- },
- ],
- done,
- );
- });
-
- it('should turn off loading on error', done => {
- testAction(
- actions.requestDeleteTag,
- {
- tag: {
- destroy_path: null,
- },
- },
- {},
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [],
- ).catch(() => done());
- });
- });
-
- describe('requestImageDetailsAndTagsList', () => {
- it('sets the imageDetails and dispatch requestTagsList', done => {
- const resolvedValue = { foo: 'bar' };
- jest.spyOn(Api, 'containerRegistryDetails').mockResolvedValue({ data: resolvedValue });
-
- testAction(
- actions.requestImageDetailsAndTagsList,
- 1,
- {},
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_IMAGE_DETAILS, payload: resolvedValue },
- ],
- [
- {
- type: 'requestTagsList',
- },
- ],
- done,
- );
- });
-
- it('should create flash on error', done => {
- jest.spyOn(Api, 'containerRegistryDetails').mockRejectedValue();
- testAction(
- actions.requestImageDetailsAndTagsList,
- 1,
- {},
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [],
- () => {
- expect(createFlash).toHaveBeenCalledWith({ message: FETCH_IMAGE_DETAILS_ERROR_MESSAGE });
- done();
- },
- );
- });
- });
-
- describe('request delete multiple tags', () => {
- it('successfully performs the delete request', done => {
- mock.onDelete(url).replyOnce(200);
-
- testAction(
- actions.requestDeleteTags,
- {
- ids: [1, 2],
- },
- {
- tagsPagination: {},
- },
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [
- {
- type: 'setShowGarbageCollectionTip',
- payload: true,
- },
- {
- type: 'requestTagsList',
- payload: {},
- },
- ],
- done,
- );
- });
-
- it('should turn off loading on error', done => {
- mock.onDelete(url).replyOnce(500);
-
- testAction(
- actions.requestDeleteTags,
- {
- ids: [1, 2],
- },
- {
- tagsPagination: {},
- },
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [],
- ).catch(() => done());
- });
- });
-
- describe('request delete single image', () => {
- const image = {
- destroy_path: 'delete/path',
- };
-
- it('successfully performs the delete request', done => {
- mock.onDelete(image.destroy_path).replyOnce(200);
-
- testAction(
- actions.requestDeleteImage,
- image,
- {},
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.UPDATE_IMAGE, payload: { ...image, deleting: true } },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [],
- done,
- );
- });
-
- it('should turn off loading on error', done => {
- mock.onDelete(image.destroy_path).replyOnce(400);
- testAction(
- actions.requestDeleteImage,
- image,
- {},
- [
- { type: types.SET_MAIN_LOADING, payload: true },
- { type: types.SET_MAIN_LOADING, payload: false },
- ],
- [],
- ).catch(() => done());
- });
- });
-});
diff --git a/spec/frontend/registry/explorer/stores/getters_spec.js b/spec/frontend/registry/explorer/stores/getters_spec.js
deleted file mode 100644
index 4cab65d2bb0..00000000000
--- a/spec/frontend/registry/explorer/stores/getters_spec.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import * as getters from '~/registry/explorer/stores/getters';
-
-describe('Getters RegistryExplorer store', () => {
- let state;
-
- describe.each`
- getter | prefix | configParameter | suffix
- ${'dockerBuildCommand'} | ${'docker build -t'} | ${'repositoryUrl'} | ${'.'}
- ${'dockerPushCommand'} | ${'docker push'} | ${'repositoryUrl'} | ${null}
- ${'dockerLoginCommand'} | ${'docker login'} | ${'registryHostUrlWithPort'} | ${null}
- `('$getter', ({ getter, prefix, configParameter, suffix }) => {
- beforeEach(() => {
- state = {
- config: { repositoryUrl: 'foo', registryHostUrlWithPort: 'bar' },
- };
- });
-
- it(`returns ${prefix} concatenated with ${configParameter} and optionally suffixed with ${suffix}`, () => {
- const expectedPieces = [prefix, state.config[configParameter], suffix].filter(p => p);
- expect(getters[getter](state)).toBe(expectedPieces.join(' '));
- });
- });
-
- describe('showGarbageCollection', () => {
- it.each`
- result | showGarbageCollectionTip | isAdmin
- ${true} | ${true} | ${true}
- ${false} | ${true} | ${false}
- ${false} | ${false} | ${true}
- `(
- 'return $result when showGarbageCollectionTip $showGarbageCollectionTip and isAdmin is $isAdmin',
- ({ result, showGarbageCollectionTip, isAdmin }) => {
- state = {
- config: { isAdmin },
- showGarbageCollectionTip,
- };
- expect(getters.showGarbageCollection(state)).toBe(result);
- },
- );
- });
-});
diff --git a/spec/frontend/registry/explorer/stores/mutations_spec.js b/spec/frontend/registry/explorer/stores/mutations_spec.js
deleted file mode 100644
index 1908d3f0350..00000000000
--- a/spec/frontend/registry/explorer/stores/mutations_spec.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import mutations from '~/registry/explorer/stores/mutations';
-import * as types from '~/registry/explorer/stores/mutation_types';
-
-describe('Mutations Registry Explorer Store', () => {
- let mockState;
-
- beforeEach(() => {
- mockState = {};
- });
-
- describe('SET_INITIAL_STATE', () => {
- it('should set the initial state', () => {
- const payload = {
- endpoint: 'foo',
- isGroupPage: '',
- expirationPolicy: { foo: 'bar' },
- isAdmin: '',
- };
- const expectedState = {
- ...mockState,
- config: { ...payload, isGroupPage: false, isAdmin: false },
- };
- mutations[types.SET_INITIAL_STATE](mockState, {
- ...payload,
- expirationPolicy: JSON.stringify(payload.expirationPolicy),
- });
-
- expect(mockState).toEqual(expectedState);
- });
- });
-
- describe('SET_IMAGES_LIST_SUCCESS', () => {
- it('should set the images list', () => {
- const images = [{ name: 'foo' }, { name: 'bar' }];
- const defaultStatus = { deleting: false, failedDelete: false };
- const expectedState = {
- ...mockState,
- images: [{ name: 'foo', ...defaultStatus }, { name: 'bar', ...defaultStatus }],
- };
- mutations[types.SET_IMAGES_LIST_SUCCESS](mockState, images);
-
- expect(mockState).toEqual(expectedState);
- });
- });
-
- describe('UPDATE_IMAGE', () => {
- it('should update an image', () => {
- mockState.images = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }];
- const payload = { id: 1, name: 'baz' };
- const expectedState = {
- ...mockState,
- images: [payload, { id: 2, name: 'bar' }],
- };
- mutations[types.UPDATE_IMAGE](mockState, payload);
-
- expect(mockState).toEqual(expectedState);
- });
- });
-
- describe('SET_TAGS_LIST_SUCCESS', () => {
- it('should set the tags list', () => {
- const tags = [1, 2, 3];
- const expectedState = { ...mockState, tags };
- mutations[types.SET_TAGS_LIST_SUCCESS](mockState, tags);
-
- expect(mockState).toEqual(expectedState);
- });
- });
-
- describe('SET_MAIN_LOADING', () => {
- it('should set the isLoading', () => {
- const expectedState = { ...mockState, isLoading: true };
- mutations[types.SET_MAIN_LOADING](mockState, true);
-
- expect(mockState).toEqual(expectedState);
- });
- });
-
- describe('SET_SHOW_GARBAGE_COLLECTION_TIP', () => {
- it('should set the showGarbageCollectionTip', () => {
- const expectedState = { ...mockState, showGarbageCollectionTip: true };
- mutations[types.SET_SHOW_GARBAGE_COLLECTION_TIP](mockState, true);
-
- expect(mockState).toEqual(expectedState);
- });
- });
-
- describe('SET_PAGINATION', () => {
- const generatePagination = () => [
- {
- 'X-PAGE': '1',
- 'X-PER-PAGE': '20',
- 'X-TOTAL': '100',
- 'X-TOTAL-PAGES': '5',
- 'X-NEXT-PAGE': '2',
- 'X-PREV-PAGE': '0',
- },
- {
- page: 1,
- perPage: 20,
- total: 100,
- totalPages: 5,
- nextPage: 2,
- previousPage: 0,
- },
- ];
-
- it('should set the images pagination', () => {
- const [headers, expectedResult] = generatePagination();
- const expectedState = { ...mockState, pagination: expectedResult };
- mutations[types.SET_PAGINATION](mockState, headers);
-
- expect(mockState).toEqual(expectedState);
- });
-
- it('should set the tags pagination', () => {
- const [headers, expectedResult] = generatePagination();
- const expectedState = { ...mockState, tagsPagination: expectedResult };
- mutations[types.SET_TAGS_PAGINATION](mockState, headers);
-
- expect(mockState).toEqual(expectedState);
- });
- });
-
- describe('SET_IMAGE_DETAILS', () => {
- it('should set imageDetails', () => {
- const expectedState = { ...mockState, imageDetails: { foo: 'bar' } };
- mutations[types.SET_IMAGE_DETAILS](mockState, { foo: 'bar' });
-
- expect(mockState).toEqual(expectedState);
- });
- });
-});
diff --git a/spec/frontend/registry/explorer/stubs.js b/spec/frontend/registry/explorer/stubs.js
index b6c0ee67757..d6fba863ee0 100644
--- a/spec/frontend/registry/explorer/stubs.js
+++ b/spec/frontend/registry/explorer/stubs.js
@@ -1,35 +1,33 @@
+import {
+ GlModal as RealGlModal,
+ GlEmptyState as RealGlEmptyState,
+ GlSkeletonLoader as RealGlSkeletonLoader,
+} from '@gitlab/ui';
+import { RouterLinkStub } from '@vue/test-utils';
+import { stubComponent } from 'helpers/stub_component';
import RealDeleteModal from '~/registry/explorer/components/details_page/delete_modal.vue';
import RealListItem from '~/vue_shared/components/registry/list_item.vue';
-export const GlModal = {
+export const GlModal = stubComponent(RealGlModal, {
template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>',
methods: {
show: jest.fn(),
},
-};
+});
-export const GlEmptyState = {
+export const GlEmptyState = stubComponent(RealGlEmptyState, {
template: '<div><slot name="description"></slot></div>',
- name: 'GlEmptyStateSTub',
-};
+});
-export const RouterLink = {
- template: `<div><slot></slot></div>`,
- props: ['to'],
-};
+export const RouterLink = RouterLinkStub;
-export const DeleteModal = {
- template: '<div></div>',
+export const DeleteModal = stubComponent(RealDeleteModal, {
methods: {
show: jest.fn(),
},
- props: RealDeleteModal.props,
-};
+});
-export const GlSkeletonLoader = {
- template: `<div><slot></slot></div>`,
- props: ['width', 'height'],
-};
+export const GlSkeletonLoader = stubComponent(RealGlSkeletonLoader);
export const ListItem = {
...RealListItem,
diff --git a/spec/frontend/registry/explorer/utils_spec.js b/spec/frontend/registry/explorer/utils_spec.js
deleted file mode 100644
index 7a5d6958a09..00000000000
--- a/spec/frontend/registry/explorer/utils_spec.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import { pathGenerator } from '~/registry/explorer/utils';
-
-describe('Utils', () => {
- describe('pathGenerator', () => {
- const imageDetails = {
- path: 'foo/bar/baz',
- name: 'baz',
- id: 1,
- };
-
- beforeEach(() => {
- window.gon.relative_url_root = null;
- });
-
- it('returns the fetch url when no ending is passed', () => {
- expect(pathGenerator(imageDetails)).toBe('/foo/bar/registry/repository/1/tags?format=json');
- });
-
- it('returns the url with an ending when is passed', () => {
- expect(pathGenerator(imageDetails, '/foo')).toBe('/foo/bar/registry/repository/1/tags/foo');
- });
-
- describe.each`
- path | name | result
- ${'foo/foo'} | ${''} | ${'/foo/foo/registry/repository/1/tags?format=json'}
- ${'foo/foo/foo'} | ${'foo'} | ${'/foo/foo/registry/repository/1/tags?format=json'}
- ${'baz/foo/foo/foo'} | ${'foo'} | ${'/baz/foo/foo/registry/repository/1/tags?format=json'}
- ${'baz/foo/foo/foo'} | ${'foo'} | ${'/baz/foo/foo/registry/repository/1/tags?format=json'}
- ${'foo/foo/baz/foo/foo'} | ${'foo/foo'} | ${'/foo/foo/baz/registry/repository/1/tags?format=json'}
- ${'foo/foo/baz/foo/bar'} | ${'foo/bar'} | ${'/foo/foo/baz/registry/repository/1/tags?format=json'}
- ${'baz/foo/foo'} | ${'foo'} | ${'/baz/foo/registry/repository/1/tags?format=json'}
- ${'baz/foo/bar'} | ${'foo'} | ${'/baz/foo/bar/registry/repository/1/tags?format=json'}
- `('when path is $path and name is $name', ({ name, path, result }) => {
- it('returns the correct value', () => {
- expect(pathGenerator({ id: 1, name, path })).toBe(result);
- });
-
- it('produces a correct relative url', () => {
- window.gon.relative_url_root = '/gitlab';
- expect(pathGenerator({ id: 1, name, path })).toBe(`/gitlab${result}`);
- });
- });
-
- it('returns the url unchanged when imageDetails have no name', () => {
- const imageDetailsWithoutName = {
- path: 'foo/bar/baz',
- name: '',
- id: 1,
- };
-
- expect(pathGenerator(imageDetailsWithoutName)).toBe(
- '/foo/bar/baz/registry/repository/1/tags?format=json',
- );
- });
- });
-});
diff --git a/spec/frontend/registry/shared/__snapshots__/utils_spec.js.snap b/spec/frontend/registry/settings/__snapshots__/utils_spec.js.snap
index 032007bba51..7062773b46b 100644
--- a/spec/frontend/registry/shared/__snapshots__/utils_spec.js.snap
+++ b/spec/frontend/registry/settings/__snapshots__/utils_spec.js.snap
@@ -76,25 +76,25 @@ Array [
Object {
"default": false,
"key": "SEVEN_DAYS",
- "label": "7 days until tags are automatically removed",
+ "label": "7 days",
"variable": 7,
},
Object {
"default": false,
"key": "FOURTEEN_DAYS",
- "label": "14 days until tags are automatically removed",
+ "label": "14 days",
"variable": 14,
},
Object {
"default": false,
"key": "THIRTY_DAYS",
- "label": "30 days until tags are automatically removed",
+ "label": "30 days",
"variable": 30,
},
Object {
"default": true,
"key": "NINETY_DAYS",
- "label": "90 days until tags are automatically removed",
+ "label": "90 days",
"variable": 90,
},
]
diff --git a/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap b/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap
new file mode 100644
index 00000000000..d7f89ce070e
--- /dev/null
+++ b/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap
@@ -0,0 +1,64 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Settings Form Cadence matches snapshot 1`] = `
+<expiration-dropdown-stub
+ class="gl-mr-7 gl-mb-0!"
+ data-testid="cadence-dropdown"
+ formoptions="[object Object],[object Object],[object Object],[object Object],[object Object]"
+ label="Run cleanup:"
+ name="cadence"
+ value="EVERY_DAY"
+/>
+`;
+
+exports[`Settings Form Enable matches snapshot 1`] = `
+<expiration-toggle-stub
+ class="gl-mb-0!"
+ data-testid="enable-toggle"
+ value="true"
+/>
+`;
+
+exports[`Settings Form Keep N matches snapshot 1`] = `
+<expiration-dropdown-stub
+ data-testid="keep-n-dropdown"
+ formoptions="[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]"
+ label="Keep the most recent:"
+ name="keep-n"
+ value="TEN_TAGS"
+/>
+`;
+
+exports[`Settings Form Keep Regex matches snapshot 1`] = `
+<expiration-input-stub
+ data-testid="keep-regex-input"
+ description="Tags with names that match this regex pattern are kept. %{linkStart}More information%{linkEnd}"
+ error=""
+ label="Keep tags matching:"
+ name="keep-regex"
+ placeholder=""
+ value="sss"
+/>
+`;
+
+exports[`Settings Form OlderThan matches snapshot 1`] = `
+<expiration-dropdown-stub
+ data-testid="older-than-dropdown"
+ formoptions="[object Object],[object Object],[object Object],[object Object]"
+ label="Remove tags older than:"
+ name="older-than"
+ value="FOURTEEN_DAYS"
+/>
+`;
+
+exports[`Settings Form Remove regex matches snapshot 1`] = `
+<expiration-input-stub
+ data-testid="remove-regex-input"
+ description="Tags with names that match this regex pattern are removed. %{linkStart}More information%{linkEnd}"
+ error=""
+ label="Remove tags matching:"
+ name="remove-regex"
+ placeholder=".*"
+ value="asdasdssssdfdf"
+/>
+`;
diff --git a/spec/frontend/registry/settings/components/expiration_dropdown_spec.js b/spec/frontend/registry/settings/components/expiration_dropdown_spec.js
new file mode 100644
index 00000000000..e0cac317ad6
--- /dev/null
+++ b/spec/frontend/registry/settings/components/expiration_dropdown_spec.js
@@ -0,0 +1,83 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlFormGroup, GlFormSelect } from 'jest/registry/shared/stubs';
+import component from '~/registry/settings/components/expiration_dropdown.vue';
+
+describe('ExpirationDropdown', () => {
+ let wrapper;
+
+ const defaultProps = {
+ name: 'foo',
+ label: 'label-bar',
+ formOptions: [{ key: 'foo', label: 'bar' }, { key: 'baz', label: 'zab' }],
+ };
+
+ const findFormSelect = () => wrapper.find(GlFormSelect);
+ const findFormGroup = () => wrapper.find(GlFormGroup);
+ const findOptions = () => wrapper.findAll('[data-testid="option"]');
+
+ const mountComponent = props => {
+ wrapper = shallowMount(component, {
+ stubs: {
+ GlFormGroup,
+ GlFormSelect,
+ },
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('structure', () => {
+ it('has a form-select component', () => {
+ mountComponent();
+ expect(findFormSelect().exists()).toBe(true);
+ });
+
+ it('has the correct options', () => {
+ mountComponent();
+
+ expect(findOptions()).toHaveLength(defaultProps.formOptions.length);
+ });
+ });
+
+ describe('model', () => {
+ it('assign the right props to the form-select component', () => {
+ const value = 'foobar';
+ const disabled = true;
+
+ mountComponent({ value, disabled });
+
+ expect(findFormSelect().props()).toMatchObject({
+ value,
+ disabled,
+ });
+ expect(findFormSelect().attributes('id')).toBe(defaultProps.name);
+ });
+
+ it('assign the right props to the form-group component', () => {
+ mountComponent();
+
+ expect(findFormGroup().attributes()).toMatchObject({
+ id: `${defaultProps.name}-form-group`,
+ 'label-for': defaultProps.name,
+ label: defaultProps.label,
+ });
+ });
+
+ it('emits input event when form-select emits input', () => {
+ const emittedValue = 'barfoo';
+
+ mountComponent();
+
+ findFormSelect().vm.$emit('input', emittedValue);
+
+ expect(wrapper.emitted('input')).toEqual([[emittedValue]]);
+ });
+ });
+});
diff --git a/spec/frontend/registry/settings/components/expiration_input_spec.js b/spec/frontend/registry/settings/components/expiration_input_spec.js
new file mode 100644
index 00000000000..849f85aa265
--- /dev/null
+++ b/spec/frontend/registry/settings/components/expiration_input_spec.js
@@ -0,0 +1,169 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlSprintf, GlFormInput, GlLink } from '@gitlab/ui';
+import { GlFormGroup } from 'jest/registry/shared/stubs';
+import component from '~/registry/settings/components/expiration_input.vue';
+import { NAME_REGEX_LENGTH } from '~/registry/settings/constants';
+
+describe('ExpirationInput', () => {
+ let wrapper;
+
+ const defaultProps = {
+ name: 'foo',
+ label: 'label-bar',
+ placeholder: 'placeholder-baz',
+ description: '%{linkStart}description-foo%{linkEnd}',
+ };
+
+ const tagsRegexHelpPagePath = 'fooPath';
+
+ const findInput = () => wrapper.find(GlFormInput);
+ const findFormGroup = () => wrapper.find(GlFormGroup);
+ const findLabel = () => wrapper.find('[data-testid="label"]');
+ const findDescription = () => wrapper.find('[data-testid="description"]');
+ const findDescriptionLink = () => wrapper.find(GlLink);
+
+ const mountComponent = props => {
+ wrapper = shallowMount(component, {
+ stubs: {
+ GlSprintf,
+ GlFormGroup,
+ },
+ provide: {
+ tagsRegexHelpPagePath,
+ },
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('structure', () => {
+ it('has a label', () => {
+ mountComponent();
+
+ expect(findLabel().text()).toBe(defaultProps.label);
+ });
+
+ it('has a textarea component', () => {
+ mountComponent();
+
+ expect(findInput().exists()).toBe(true);
+ });
+
+ it('has a description', () => {
+ mountComponent();
+
+ expect(findDescription().text()).toMatchInterpolatedText(defaultProps.description);
+ });
+
+ it('has a description link', () => {
+ mountComponent();
+
+ const link = findDescriptionLink();
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(tagsRegexHelpPagePath);
+ });
+ });
+
+ describe('model', () => {
+ it('assigns the right props to the textarea component', () => {
+ const value = 'foobar';
+ const disabled = true;
+
+ mountComponent({ value, disabled });
+
+ expect(findInput().attributes()).toMatchObject({
+ id: defaultProps.name,
+ value,
+ placeholder: defaultProps.placeholder,
+ disabled: `${disabled}`,
+ trim: '',
+ });
+ });
+
+ it('emits input event when textarea emits input', () => {
+ const emittedValue = 'barfoo';
+
+ mountComponent();
+
+ findInput().vm.$emit('input', emittedValue);
+ expect(wrapper.emitted('input')).toEqual([[emittedValue]]);
+ });
+ });
+
+ describe('regex textarea validation', () => {
+ const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(',');
+
+ describe('when error contains an error message', () => {
+ const errorMessage = 'something went wrong';
+
+ it('shows the error message on the relevant field', () => {
+ mountComponent({ error: errorMessage });
+
+ expect(findFormGroup().attributes('invalid-feedback')).toBe(errorMessage);
+ });
+
+ it('gives precedence to API errors compared to local ones', () => {
+ mountComponent({
+ error: errorMessage,
+ value: invalidString,
+ });
+
+ expect(findFormGroup().attributes('invalid-feedback')).toBe(errorMessage);
+ });
+ });
+
+ describe('when error is empty', () => {
+ describe('if the user did not type', () => {
+ it('validation is not emitted', () => {
+ mountComponent();
+
+ expect(wrapper.emitted('validation')).toBeUndefined();
+ });
+
+ it('no error message is shown', () => {
+ mountComponent();
+
+ expect(findFormGroup().props('state')).toBe(true);
+ expect(findFormGroup().attributes('invalid-feedback')).toBe('');
+ });
+ });
+
+ describe('when the user typed something', () => {
+ describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => {
+ beforeEach(() => {
+ // since the component has no state we both emit the event and set the prop
+ mountComponent({ value: invalidString });
+
+ findInput().vm.$emit('input', invalidString);
+ });
+
+ it('textAreaValidation state is false', () => {
+ expect(findFormGroup().props('state')).toBe(false);
+ expect(findInput().attributes('state')).toBeUndefined();
+ });
+
+ it('emits the @validation event with false payload', () => {
+ expect(wrapper.emitted('validation')).toEqual([[false]]);
+ });
+ });
+
+ it(`when user input is less than ${NAME_REGEX_LENGTH} state is "true"`, () => {
+ mountComponent();
+
+ findInput().vm.$emit('input', 'foo');
+
+ expect(findFormGroup().props('state')).toBe(true);
+ expect(findInput().attributes('state')).toBe('true');
+ expect(wrapper.emitted('validation')).toEqual([[true]]);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/registry/settings/components/expiration_run_text_spec.js b/spec/frontend/registry/settings/components/expiration_run_text_spec.js
new file mode 100644
index 00000000000..c594b1f449d
--- /dev/null
+++ b/spec/frontend/registry/settings/components/expiration_run_text_spec.js
@@ -0,0 +1,62 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlFormInput } from '@gitlab/ui';
+import { GlFormGroup } from 'jest/registry/shared/stubs';
+import component from '~/registry/settings/components/expiration_run_text.vue';
+import { NEXT_CLEANUP_LABEL, NOT_SCHEDULED_POLICY_TEXT } from '~/registry/settings/constants';
+
+describe('ExpirationToggle', () => {
+ let wrapper;
+ const value = 'foo';
+
+ const findInput = () => wrapper.find(GlFormInput);
+ const findFormGroup = () => wrapper.find(GlFormGroup);
+
+ const mountComponent = propsData => {
+ wrapper = shallowMount(component, {
+ stubs: {
+ GlFormGroup,
+ },
+ propsData,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('structure', () => {
+ it('has an input component', () => {
+ mountComponent();
+
+ expect(findInput().exists()).toBe(true);
+ });
+ });
+
+ describe('model', () => {
+ it('assigns the right props to the form-group component', () => {
+ mountComponent();
+
+ expect(findFormGroup().attributes()).toMatchObject({
+ label: NEXT_CLEANUP_LABEL,
+ });
+ });
+ });
+
+ describe('formattedValue', () => {
+ it.each`
+ valueProp | enabled | expected
+ ${value} | ${true} | ${value}
+ ${value} | ${false} | ${NOT_SCHEDULED_POLICY_TEXT}
+ ${undefined} | ${false} | ${NOT_SCHEDULED_POLICY_TEXT}
+ ${undefined} | ${true} | ${NOT_SCHEDULED_POLICY_TEXT}
+ `(
+ 'when value is $valueProp and enabled is $enabled the input value is $expected',
+ ({ valueProp, enabled, expected }) => {
+ mountComponent({ value: valueProp, enabled });
+
+ expect(findInput().attributes('value')).toBe(expected);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/registry/settings/components/expiration_toggle_spec.js b/spec/frontend/registry/settings/components/expiration_toggle_spec.js
new file mode 100644
index 00000000000..99ff7a7f77a
--- /dev/null
+++ b/spec/frontend/registry/settings/components/expiration_toggle_spec.js
@@ -0,0 +1,77 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlToggle, GlSprintf } from '@gitlab/ui';
+import { GlFormGroup } from 'jest/registry/shared/stubs';
+import component from '~/registry/settings/components/expiration_toggle.vue';
+import {
+ ENABLED_TOGGLE_DESCRIPTION,
+ DISABLED_TOGGLE_DESCRIPTION,
+} from '~/registry/settings/constants';
+
+describe('ExpirationToggle', () => {
+ let wrapper;
+
+ const findToggle = () => wrapper.find(GlToggle);
+ const findDescription = () => wrapper.find('[data-testid="description"]');
+
+ const mountComponent = propsData => {
+ wrapper = shallowMount(component, {
+ stubs: {
+ GlFormGroup,
+ GlSprintf,
+ },
+ propsData,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('structure', () => {
+ it('has a toggle component', () => {
+ mountComponent();
+
+ expect(findToggle().exists()).toBe(true);
+ });
+
+ it('has a description', () => {
+ mountComponent();
+
+ expect(findDescription().exists()).toBe(true);
+ });
+ });
+
+ describe('model', () => {
+ it('assigns the right props to the toggle component', () => {
+ mountComponent({ value: true, disabled: true });
+
+ expect(findToggle().props()).toMatchObject({
+ value: true,
+ disabled: true,
+ });
+ });
+
+ it('emits input event when toggle is updated', () => {
+ mountComponent();
+
+ findToggle().vm.$emit('change', false);
+
+ expect(wrapper.emitted('input')).toEqual([[false]]);
+ });
+ });
+
+ describe('toggle description', () => {
+ it('says enabled when the toggle is on', () => {
+ mountComponent({ value: true });
+
+ expect(findDescription().text()).toMatchInterpolatedText(ENABLED_TOGGLE_DESCRIPTION);
+ });
+
+ it('says disabled when the toggle is off', () => {
+ mountComponent({ value: false });
+
+ expect(findDescription().text()).toMatchInterpolatedText(DISABLED_TOGGLE_DESCRIPTION);
+ });
+ });
+});
diff --git a/spec/frontend/registry/settings/components/registry_settings_app_spec.js b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
index a784396f47a..c31c7bdf99b 100644
--- a/spec/frontend/registry/settings/components/registry_settings_app_spec.js
+++ b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
@@ -3,15 +3,19 @@ import VueApollo from 'vue-apollo';
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import component from '~/registry/settings/components/registry_settings_app.vue';
-import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.graphql';
+import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
import SettingsForm from '~/registry/settings/components/settings_form.vue';
-import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/shared/constants';
import {
+ FETCH_SETTINGS_ERROR_MESSAGE,
UNAVAILABLE_FEATURE_INTRO_TEXT,
UNAVAILABLE_USER_FEATURE_TEXT,
} from '~/registry/settings/constants';
-import { expirationPolicyPayload, emptyExpirationPolicyPayload } from '../mock_data';
+import {
+ expirationPolicyPayload,
+ emptyExpirationPolicyPayload,
+ containerExpirationPolicyData,
+} from '../mock_data';
const localVue = createLocalVue();
@@ -62,6 +66,29 @@ describe('Registry Settings App', () => {
wrapper.destroy();
});
+ describe('isEdited status', () => {
+ it.each`
+ description | apiResponse | workingCopy | result
+ ${'empty response and no changes from user'} | ${emptyExpirationPolicyPayload()} | ${{}} | ${false}
+ ${'empty response and changes from user'} | ${emptyExpirationPolicyPayload()} | ${{ enabled: true }} | ${true}
+ ${'response and no changes'} | ${expirationPolicyPayload()} | ${containerExpirationPolicyData()} | ${false}
+ ${'response and changes'} | ${expirationPolicyPayload()} | ${{ ...containerExpirationPolicyData(), nameRegex: '12345' }} | ${true}
+ ${'response and empty'} | ${expirationPolicyPayload()} | ${{}} | ${true}
+ `('$description', async ({ apiResponse, workingCopy, result }) => {
+ const requests = mountComponentWithApollo({
+ provide: { ...defaultProvidedValues, enableHistoricEntries: true },
+ resolver: jest.fn().mockResolvedValue(apiResponse),
+ });
+ await Promise.all(requests);
+
+ findSettingsComponent().vm.$emit('input', workingCopy);
+
+ await wrapper.vm.$nextTick();
+
+ expect(findSettingsComponent().props('isEdited')).toBe(result);
+ });
+ });
+
it('renders the setting form', async () => {
const requests = mountComponentWithApollo({
resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()),
diff --git a/spec/frontend/registry/settings/components/settings_form_spec.js b/spec/frontend/registry/settings/components/settings_form_spec.js
index 4346cfadcc8..b89269c0ae4 100644
--- a/spec/frontend/registry/settings/components/settings_form_spec.js
+++ b/spec/frontend/registry/settings/components/settings_form_spec.js
@@ -4,13 +4,12 @@ import createMockApollo from 'jest/helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Tracking from '~/tracking';
import component from '~/registry/settings/components/settings_form.vue';
-import expirationPolicyFields from '~/registry/shared/components/expiration_policy_fields.vue';
-import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.graphql';
-import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.graphql';
+import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.mutation.graphql';
+import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
import {
UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE,
-} from '~/registry/shared/constants';
+} from '~/registry/settings/constants';
import { GlCard, GlLoadingIcon } from '../../shared/stubs';
import { expirationPolicyPayload, expirationPolicyMutationPayload } from '../mock_data';
@@ -39,9 +38,15 @@ describe('Settings Form', () => {
};
const findForm = () => wrapper.find({ ref: 'form-element' });
- const findFields = () => wrapper.find(expirationPolicyFields);
- const findCancelButton = () => wrapper.find({ ref: 'cancel-button' });
- const findSaveButton = () => wrapper.find({ ref: 'save-button' });
+
+ const findCancelButton = () => wrapper.find('[data-testid="cancel-button"');
+ const findSaveButton = () => wrapper.find('[data-testid="save-button"');
+ const findEnableToggle = () => wrapper.find('[data-testid="enable-toggle"]');
+ const findCadenceDropdown = () => wrapper.find('[data-testid="cadence-dropdown"]');
+ const findKeepNDropdown = () => wrapper.find('[data-testid="keep-n-dropdown"]');
+ const findKeepRegexInput = () => wrapper.find('[data-testid="keep-regex-input"]');
+ const findOlderThanDropdown = () => wrapper.find('[data-testid="older-than-dropdown"]');
+ const findRemoveRegexInput = () => wrapper.find('[data-testid="remove-regex-input"]');
const mountComponent = ({
props = defaultProps,
@@ -109,45 +114,136 @@ describe('Settings Form', () => {
wrapper.destroy();
});
- describe('data binding', () => {
- it('v-model change update the settings property', () => {
+ describe.each`
+ model | finder | fieldName | type | defaultValue
+ ${'enabled'} | ${findEnableToggle} | ${'Enable'} | ${'toggle'} | ${false}
+ ${'cadence'} | ${findCadenceDropdown} | ${'Cadence'} | ${'dropdown'} | ${'EVERY_DAY'}
+ ${'keepN'} | ${findKeepNDropdown} | ${'Keep N'} | ${'dropdown'} | ${'TEN_TAGS'}
+ ${'nameRegexKeep'} | ${findKeepRegexInput} | ${'Keep Regex'} | ${'textarea'} | ${''}
+ ${'olderThan'} | ${findOlderThanDropdown} | ${'OlderThan'} | ${'dropdown'} | ${'NINETY_DAYS'}
+ ${'nameRegex'} | ${findRemoveRegexInput} | ${'Remove regex'} | ${'textarea'} | ${''}
+ `('$fieldName', ({ model, finder, type, defaultValue }) => {
+ it('matches snapshot', () => {
mountComponent();
- findFields().vm.$emit('input', { newValue: 'foo' });
- expect(wrapper.emitted('input')).toEqual([['foo']]);
+
+ expect(finder().element).toMatchSnapshot();
});
- it('v-model change update the api error property', () => {
- const apiErrors = { baz: 'bar' };
- mountComponent({ data: { apiErrors } });
- expect(findFields().props('apiErrors')).toEqual(apiErrors);
- findFields().vm.$emit('input', { newValue: 'foo', modified: 'baz' });
- expect(findFields().props('apiErrors')).toEqual({});
+ it('input event triggers a model update', () => {
+ mountComponent();
+
+ finder().vm.$emit('input', 'foo');
+ expect(wrapper.emitted('input')[0][0]).toMatchObject({
+ [model]: 'foo',
+ });
});
it('shows the default option when none are selected', () => {
mountComponent({ props: { value: {} } });
- expect(findFields().props('value')).toEqual({
- cadence: 'EVERY_DAY',
- keepN: 'TEN_TAGS',
- olderThan: 'NINETY_DAYS',
- });
+ expect(finder().props('value')).toEqual(defaultValue);
});
+
+ if (type !== 'toggle') {
+ it.each`
+ isLoading | mutationLoading | enabledValue
+ ${false} | ${false} | ${false}
+ ${true} | ${false} | ${false}
+ ${true} | ${true} | ${true}
+ ${false} | ${true} | ${true}
+ ${false} | ${false} | ${false}
+ `(
+ 'is disabled when is loading is $isLoading, mutationLoading is $mutationLoading and enabled is $enabledValue',
+ ({ isLoading, mutationLoading, enabledValue }) => {
+ mountComponent({
+ props: { isLoading, value: { enabled: enabledValue } },
+ data: { mutationLoading },
+ });
+ expect(finder().props('disabled')).toEqual(true);
+ },
+ );
+ } else {
+ it.each`
+ isLoading | mutationLoading
+ ${true} | ${false}
+ ${true} | ${true}
+ ${false} | ${true}
+ `(
+ 'is disabled when is loading is $isLoading and mutationLoading is $mutationLoading',
+ ({ isLoading, mutationLoading }) => {
+ mountComponent({
+ props: { isLoading, value: {} },
+ data: { mutationLoading },
+ });
+ expect(finder().props('disabled')).toEqual(true);
+ },
+ );
+ }
+
+ if (type === 'textarea') {
+ it('input event updates the api error property', async () => {
+ const apiErrors = { [model]: 'bar' };
+ mountComponent({ data: { apiErrors } });
+
+ finder().vm.$emit('input', 'foo');
+ expect(finder().props('error')).toEqual('bar');
+
+ await wrapper.vm.$nextTick();
+
+ expect(finder().props('error')).toEqual('');
+ });
+
+ it('validation event updates buttons disabled state', async () => {
+ mountComponent();
+
+ expect(findSaveButton().props('disabled')).toBe(false);
+
+ finder().vm.$emit('validation', false);
+
+ await wrapper.vm.$nextTick();
+
+ expect(findSaveButton().props('disabled')).toBe(true);
+ });
+ }
+
+ if (type === 'dropdown') {
+ it('has the correct formOptions', () => {
+ mountComponent();
+ expect(finder().props('formOptions')).toEqual(wrapper.vm.$options.formOptions[model]);
+ });
+ }
});
describe('form', () => {
describe('form reset event', () => {
- beforeEach(() => {
+ it('calls the appropriate function', () => {
mountComponent();
findForm().trigger('reset');
- });
- it('calls the appropriate function', () => {
+
expect(wrapper.emitted('reset')).toEqual([[]]);
});
it('tracks the reset event', () => {
+ mountComponent();
+
+ findForm().trigger('reset');
+
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'reset_form', trackingPayload);
});
+
+ it('resets the errors objects', async () => {
+ mountComponent({
+ data: { apiErrors: { nameRegex: 'bar' }, localErrors: { nameRegexKeep: false } },
+ });
+
+ findForm().trigger('reset');
+
+ await wrapper.vm.$nextTick();
+
+ expect(findKeepRegexInput().props('error')).toBe('');
+ expect(findRemoveRegexInput().props('error')).toBe('');
+ expect(findSaveButton().props('disabled')).toBe(false);
+ });
});
describe('form submit event ', () => {
@@ -209,6 +305,7 @@ describe('Settings Form', () => {
});
});
});
+
describe('global errors', () => {
it('shows an error', async () => {
const handlers = mountComponentWithApollo({
@@ -230,7 +327,7 @@ describe('Settings Form', () => {
graphQLErrors: [
{
extensions: {
- problems: [{ path: ['name'], message: 'baz' }],
+ problems: [{ path: ['nameRegexKeep'], message: 'baz' }],
},
},
],
@@ -241,7 +338,7 @@ describe('Settings Form', () => {
await waitForPromises();
await wrapper.vm.$nextTick();
- expect(findFields().props('apiErrors')).toEqual({ name: 'baz' });
+ expect(findKeepRegexInput().props('error')).toEqual('baz');
});
});
});
@@ -257,23 +354,21 @@ describe('Settings Form', () => {
});
it.each`
- isLoading | isEdited | mutationLoading | isDisabled
- ${true} | ${true} | ${true} | ${true}
- ${false} | ${true} | ${true} | ${true}
- ${false} | ${false} | ${true} | ${true}
- ${true} | ${false} | ${false} | ${true}
- ${false} | ${false} | ${false} | ${true}
- ${false} | ${true} | ${false} | ${false}
+ isLoading | isEdited | mutationLoading
+ ${true} | ${true} | ${true}
+ ${false} | ${true} | ${true}
+ ${false} | ${false} | ${true}
+ ${true} | ${false} | ${false}
+ ${false} | ${false} | ${false}
`(
- 'when isLoading is $isLoading and isEdited is $isEdited and mutationLoading is $mutationLoading is $isDisabled that the is disabled',
- ({ isEdited, isLoading, mutationLoading, isDisabled }) => {
+ 'when isLoading is $isLoading, isEdited is $isEdited and mutationLoading is $mutationLoading is disabled',
+ ({ isEdited, isLoading, mutationLoading }) => {
mountComponent({
props: { ...defaultProps, isEdited, isLoading },
data: { mutationLoading },
});
- const expectation = isDisabled ? 'true' : undefined;
- expect(findCancelButton().attributes('disabled')).toBe(expectation);
+ expect(findCancelButton().props('disabled')).toBe(true);
},
);
});
@@ -284,24 +379,24 @@ describe('Settings Form', () => {
expect(findSaveButton().attributes('type')).toBe('submit');
});
+
it.each`
- isLoading | fieldsAreValid | mutationLoading | isDisabled
- ${true} | ${true} | ${true} | ${true}
- ${false} | ${true} | ${true} | ${true}
- ${false} | ${false} | ${true} | ${true}
- ${true} | ${false} | ${false} | ${true}
- ${false} | ${false} | ${false} | ${true}
- ${false} | ${true} | ${false} | ${false}
+ isLoading | localErrors | mutationLoading
+ ${true} | ${{}} | ${true}
+ ${true} | ${{}} | ${false}
+ ${false} | ${{}} | ${true}
+ ${false} | ${{ foo: false }} | ${true}
+ ${true} | ${{ foo: false }} | ${false}
+ ${false} | ${{ foo: false }} | ${false}
`(
- 'when isLoading is $isLoading and fieldsAreValid is $fieldsAreValid and mutationLoading is $mutationLoading is $isDisabled that the is disabled',
- ({ fieldsAreValid, isLoading, mutationLoading, isDisabled }) => {
+ 'when isLoading is $isLoading, localErrors is $localErrors and mutationLoading is $mutationLoading is disabled',
+ ({ localErrors, isLoading, mutationLoading }) => {
mountComponent({
props: { ...defaultProps, isLoading },
- data: { mutationLoading, fieldsAreValid },
+ data: { mutationLoading, localErrors },
});
- const expectation = isDisabled ? 'true' : undefined;
- expect(findSaveButton().attributes('disabled')).toBe(expectation);
+ expect(findSaveButton().props('disabled')).toBe(true);
},
);
diff --git a/spec/frontend/registry/settings/graphql/cache_updated_spec.js b/spec/frontend/registry/settings/graphql/cache_updated_spec.js
index e5f69a08285..d88a5576f26 100644
--- a/spec/frontend/registry/settings/graphql/cache_updated_spec.js
+++ b/spec/frontend/registry/settings/graphql/cache_updated_spec.js
@@ -1,5 +1,5 @@
import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update';
-import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.graphql';
+import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
describe('Registry settings cache update', () => {
let client;
diff --git a/spec/frontend/registry/settings/mock_data.js b/spec/frontend/registry/settings/mock_data.js
index 7f3772ce7fe..7cc645fcf55 100644
--- a/spec/frontend/registry/settings/mock_data.js
+++ b/spec/frontend/registry/settings/mock_data.js
@@ -1,13 +1,18 @@
+export const containerExpirationPolicyData = () => ({
+ cadence: 'EVERY_DAY',
+ enabled: true,
+ keepN: 'TEN_TAGS',
+ nameRegex: 'asdasdssssdfdf',
+ nameRegexKeep: 'sss',
+ olderThan: 'FOURTEEN_DAYS',
+ nextRunAt: '2020-11-19T07:37:03.941Z',
+});
+
export const expirationPolicyPayload = override => ({
data: {
project: {
containerExpirationPolicy: {
- cadence: 'EVERY_DAY',
- enabled: true,
- keepN: 'TEN_TAGS',
- nameRegex: 'asdasdssssdfdf',
- nameRegexKeep: 'sss',
- olderThan: 'FOURTEEN_DAYS',
+ ...containerExpirationPolicyData(),
...override,
},
},
@@ -26,12 +31,7 @@ export const expirationPolicyMutationPayload = ({ override, errors = [] } = {})
data: {
updateContainerExpirationPolicy: {
containerExpirationPolicy: {
- cadence: 'EVERY_DAY',
- enabled: true,
- keepN: 'TEN_TAGS',
- nameRegex: 'asdasdssssdfdf',
- nameRegexKeep: 'sss',
- olderThan: 'FOURTEEN_DAYS',
+ ...containerExpirationPolicyData(),
...override,
},
errors,
diff --git a/spec/frontend/registry/shared/utils_spec.js b/spec/frontend/registry/settings/utils_spec.js
index edb0c3261be..f92d51db307 100644
--- a/spec/frontend/registry/shared/utils_spec.js
+++ b/spec/frontend/registry/settings/utils_spec.js
@@ -2,7 +2,7 @@ import {
formOptionsGenerator,
optionLabelGenerator,
olderThanTranslationGenerator,
-} from '~/registry/shared/utils';
+} from '~/registry/settings/utils';
describe('Utils', () => {
describe('optionLabelGenerator', () => {
@@ -11,10 +11,7 @@ describe('Utils', () => {
[{ variable: 1 }, { variable: 2 }],
olderThanTranslationGenerator,
);
- expect(result).toEqual([
- { variable: 1, label: '1 day until tags are automatically removed' },
- { variable: 2, label: '2 days until tags are automatically removed' },
- ]);
+ expect(result).toEqual([{ variable: 1, label: '1 day' }, { variable: 2, label: '2 days' }]);
});
});
diff --git a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap
deleted file mode 100644
index 2ceb2655d40..00000000000
--- a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap
+++ /dev/null
@@ -1,148 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Expiration Policy Form renders 1`] = `
-<div
- class="gl-line-height-20"
->
- <gl-form-group-stub
- id="expiration-policy-toggle-group"
- label="Cleanup policy:"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-toggle"
- >
- <div
- class="gl-display-flex"
- >
- <gl-toggle-stub
- id="expiration-policy-toggle"
- labelposition="top"
- />
-
- <span
- class="gl-mb-3 gl-ml-3 gl-line-height-20"
- >
- <strong>
- Disabled
- </strong>
- - Tags matching the patterns defined below will be scheduled for deletion
- </span>
- </div>
- </gl-form-group-stub>
-
- <gl-form-group-stub
- id="expiration-policy-interval-group"
- label="Expiration interval:"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-interval"
- >
- <gl-form-select-stub
- disabled="true"
- id="expiration-policy-interval"
- >
- <option
- value="foo"
- >
-
- Foo
-
- </option>
- <option
- value="bar"
- >
-
- Bar
-
- </option>
- </gl-form-select-stub>
- </gl-form-group-stub>
- <gl-form-group-stub
- id="expiration-policy-schedule-group"
- label="Expiration schedule:"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-schedule"
- >
- <gl-form-select-stub
- disabled="true"
- id="expiration-policy-schedule"
- >
- <option
- value="foo"
- >
-
- Foo
-
- </option>
- <option
- value="bar"
- >
-
- Bar
-
- </option>
- </gl-form-select-stub>
- </gl-form-group-stub>
- <gl-form-group-stub
- id="expiration-policy-latest-group"
- label="Number of tags to retain:"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-latest"
- >
- <gl-form-select-stub
- disabled="true"
- id="expiration-policy-latest"
- >
- <option
- value="foo"
- >
-
- Foo
-
- </option>
- <option
- value="bar"
- >
-
- Bar
-
- </option>
- </gl-form-select-stub>
- </gl-form-group-stub>
-
- <gl-form-group-stub
- id="expiration-policy-name-matching-group"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-name-matching"
- >
-
- <gl-form-textarea-stub
- disabled="true"
- id="expiration-policy-name-matching"
- noresize="true"
- placeholder=""
- trim=""
- value=""
- />
- </gl-form-group-stub>
- <gl-form-group-stub
- id="expiration-policy-keep-name-group"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-keep-name"
- >
-
- <gl-form-textarea-stub
- disabled="true"
- id="expiration-policy-keep-name"
- noresize="true"
- placeholder=""
- trim=""
- value=""
- />
- </gl-form-group-stub>
-</div>
-`;
diff --git a/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js b/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js
deleted file mode 100644
index bee9bca5369..00000000000
--- a/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js
+++ /dev/null
@@ -1,202 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlSprintf } from '@gitlab/ui';
-import component from '~/registry/shared/components/expiration_policy_fields.vue';
-
-import { NAME_REGEX_LENGTH, ENABLED_TEXT, DISABLED_TEXT } from '~/registry/shared/constants';
-import { formOptions } from '../mock_data';
-
-describe('Expiration Policy Form', () => {
- let wrapper;
-
- const FORM_ELEMENTS_ID_PREFIX = '#expiration-policy';
-
- const findFormGroup = name => wrapper.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}-group`);
- const findFormElements = (name, parent = wrapper) =>
- parent.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}`);
-
- const mountComponent = props => {
- wrapper = shallowMount(component, {
- stubs: {
- GlSprintf,
- },
- propsData: {
- formOptions,
- ...props,
- },
- methods: {
- // override idGenerator to avoid having to test with dynamic uid
- idGenerator: value => value,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders', () => {
- mountComponent();
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe.each`
- elementName | modelName | value | disabledByToggle
- ${'toggle'} | ${'enabled'} | ${true} | ${'not disabled'}
- ${'interval'} | ${'olderThan'} | ${'foo'} | ${'disabled'}
- ${'schedule'} | ${'cadence'} | ${'foo'} | ${'disabled'}
- ${'latest'} | ${'keepN'} | ${'foo'} | ${'disabled'}
- ${'name-matching'} | ${'nameRegex'} | ${'foo'} | ${'disabled'}
- ${'keep-name'} | ${'nameRegexKeep'} | ${'bar'} | ${'disabled'}
- `(
- `${FORM_ELEMENTS_ID_PREFIX}-$elementName form element`,
- ({ elementName, modelName, value, disabledByToggle }) => {
- it(`${elementName} form group exist in the dom`, () => {
- mountComponent();
- const formGroup = findFormGroup(elementName);
- expect(formGroup.exists()).toBe(true);
- });
-
- it(`${elementName} form group has a label-for property`, () => {
- mountComponent();
- const formGroup = findFormGroup(elementName);
- expect(formGroup.attributes('label-for')).toBe(`expiration-policy-${elementName}`);
- });
-
- it(`${elementName} form group has a label-cols property`, () => {
- mountComponent({ labelCols: '1' });
- const formGroup = findFormGroup(elementName);
- return wrapper.vm.$nextTick().then(() => {
- expect(formGroup.attributes('label-cols')).toBe('1');
- });
- });
-
- it(`${elementName} form group has a label-align property`, () => {
- mountComponent({ labelAlign: 'foo' });
- const formGroup = findFormGroup(elementName);
- return wrapper.vm.$nextTick().then(() => {
- expect(formGroup.attributes('label-align')).toBe('foo');
- });
- });
-
- it(`${elementName} form group contains an input element`, () => {
- mountComponent();
- const formGroup = findFormGroup(elementName);
- expect(findFormElements(elementName, formGroup).exists()).toBe(true);
- });
-
- it(`${elementName} form element change updated ${modelName} with ${value}`, () => {
- mountComponent();
- const formGroup = findFormGroup(elementName);
- const element = findFormElements(elementName, formGroup);
-
- const modelUpdateEvent = element.vm.$options.model
- ? element.vm.$options.model.event
- : 'input';
- element.vm.$emit(modelUpdateEvent, value);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('input')).toEqual([
- [{ newValue: { [modelName]: value }, modified: modelName }],
- ]);
- });
- });
-
- it(`${elementName} is ${disabledByToggle} by enabled set to false`, () => {
- mountComponent({ settings: { enabled: false } });
- const formGroup = findFormGroup(elementName);
- const expectation = disabledByToggle === 'disabled' ? 'true' : undefined;
- expect(findFormElements(elementName, formGroup).attributes('disabled')).toBe(expectation);
- });
- },
- );
-
- describe('when isLoading is true', () => {
- beforeEach(() => {
- mountComponent({ isLoading: true });
- });
-
- it.each`
- elementName
- ${'toggle'}
- ${'interval'}
- ${'schedule'}
- ${'latest'}
- ${'name-matching'}
- ${'keep-name'}
- `(`${FORM_ELEMENTS_ID_PREFIX}-$elementName is disabled`, ({ elementName }) => {
- expect(findFormElements(elementName).attributes('disabled')).toBe('true');
- });
- });
-
- describe.each`
- modelName | elementName
- ${'nameRegex'} | ${'name-matching'}
- ${'nameRegexKeep'} | ${'keep-name'}
- `('regex textarea validation', ({ modelName, elementName }) => {
- const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(',');
-
- describe('when apiError contains an error message', () => {
- const errorMessage = 'something went wrong';
-
- it('shows the error message on the relevant field', () => {
- mountComponent({ apiErrors: { [modelName]: errorMessage } });
- expect(findFormGroup(elementName).attributes('invalid-feedback')).toBe(errorMessage);
- });
-
- it('gives precedence to API errors compared to local ones', () => {
- mountComponent({
- apiErrors: { [modelName]: errorMessage },
- value: { [modelName]: invalidString },
- });
- expect(findFormGroup(elementName).attributes('invalid-feedback')).toBe(errorMessage);
- });
- });
-
- describe('when apiErrors is empty', () => {
- it('if the user did not type validation is null', async () => {
- mountComponent({ value: { [modelName]: '' } });
- expect(findFormGroup(elementName).attributes('state')).toBeUndefined();
- expect(wrapper.emitted('validated')).toBeTruthy();
- });
-
- it(`if the user typed and is less than ${NAME_REGEX_LENGTH} state is true`, () => {
- mountComponent({ value: { [modelName]: 'foo' } });
-
- const formGroup = findFormGroup(elementName);
- const formElement = findFormElements(elementName, formGroup);
- expect(formGroup.attributes('state')).toBeTruthy();
- expect(formElement.attributes('state')).toBeTruthy();
- });
-
- describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => {
- beforeEach(() => {
- mountComponent({ value: { [modelName]: invalidString } });
- });
-
- it('textAreaValidation state is false', () => {
- expect(findFormGroup(elementName).attributes('state')).toBeUndefined();
- // we are forced to check the model attribute because falsy attrs are all casted to undefined in attrs
- // while in this case false shows an error and null instead shows nothing.
- expect(wrapper.vm.textAreaValidation[modelName].state).toBe(false);
- });
-
- it('emit the @invalidated event', () => {
- expect(wrapper.emitted('invalidated')).toBeTruthy();
- });
- });
- });
- });
-
- describe('help text', () => {
- it('toggleDescriptionText show disabled when settings.enabled is false', () => {
- mountComponent();
- const toggleHelpText = findFormGroup('toggle').find('span');
- expect(toggleHelpText.html()).toContain(DISABLED_TEXT);
- });
-
- it('toggleDescriptionText show enabled when settings.enabled is true', () => {
- mountComponent({ value: { enabled: true } });
- const toggleHelpText = findFormGroup('toggle').find('span');
- expect(toggleHelpText.html()).toContain(ENABLED_TEXT);
- });
- });
-});
diff --git a/spec/frontend/registry/shared/mock_data.js b/spec/frontend/registry/shared/mock_data.js
deleted file mode 100644
index 411363c2c95..00000000000
--- a/spec/frontend/registry/shared/mock_data.js
+++ /dev/null
@@ -1,12 +0,0 @@
-export const options = [{ key: 'foo', label: 'Foo' }, { key: 'bar', label: 'Bar', default: true }];
-export const stringifiedOptions = JSON.stringify(options);
-export const stringifiedFormOptions = {
- cadenceOptions: stringifiedOptions,
- keepNOptions: stringifiedOptions,
- olderThanOptions: stringifiedOptions,
-};
-export const formOptions = {
- cadence: options,
- keepN: options,
- olderThan: options,
-};
diff --git a/spec/frontend/registry/shared/stubs.js b/spec/frontend/registry/shared/stubs.js
index f6b88d70e49..ad41eb42df4 100644
--- a/spec/frontend/registry/shared/stubs.js
+++ b/spec/frontend/registry/shared/stubs.js
@@ -9,3 +9,23 @@ export const GlCard = {
</div>
`,
};
+
+export const GlFormGroup = {
+ name: 'gl-form-group-stub',
+ props: ['state'],
+ template: `
+ <div>
+ <slot name="label"></slot>
+ <slot></slot>
+ <slot name="description"></slot>
+ </div>`,
+};
+
+export const GlFormSelect = {
+ name: 'gl-form-select-stub',
+ props: ['disabled', 'value'],
+ template: `
+ <div>
+ <slot></slot>
+ </div>`,
+};
diff --git a/spec/frontend/reports/components/grouped_test_reports_app_spec.js b/spec/frontend/reports/components/grouped_test_reports_app_spec.js
index ae2718db17f..66d429017b2 100644
--- a/spec/frontend/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/frontend/reports/components/grouped_test_reports_app_spec.js
@@ -264,7 +264,9 @@ describe('Grouped test reports app', () => {
});
it('renders the recent failures count on the test case', () => {
- expect(findIssueDescription().text()).toContain('Failed 8 times in the last 14 days');
+ expect(findIssueDescription().text()).toContain(
+ 'Failed 8 times in master in the last 14 days',
+ );
});
});
diff --git a/spec/frontend/reports/mock_data/recent_failures_report.json b/spec/frontend/reports/mock_data/recent_failures_report.json
index a47bc30a8e5..bc86d788ee2 100644
--- a/spec/frontend/reports/mock_data/recent_failures_report.json
+++ b/spec/frontend/reports/mock_data/recent_failures_report.json
@@ -10,7 +10,10 @@
"name": "Test#sum when a is 1 and b is 2 returns summary",
"execution_time": 0.009411,
"system_output": "Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in <top (required)>'",
- "recent_failures": 8
+ "recent_failures": {
+ "count": 8,
+ "base_branch": "master"
+ }
},
{
"result": "failure",
@@ -33,7 +36,10 @@
"result": "failure",
"name": "Test#sum when a is 100 and b is 200 returns summary",
"execution_time": 0.000562,
- "recent_failures": 3
+ "recent_failures": {
+ "count": 3,
+ "base_branch": "master"
+ }
}
],
"resolved_failures": [],
diff --git a/spec/frontend/reports/store/mutations_spec.js b/spec/frontend/reports/store/mutations_spec.js
index 82a399c876d..c1c5862a37c 100644
--- a/spec/frontend/reports/store/mutations_spec.js
+++ b/spec/frontend/reports/store/mutations_spec.js
@@ -46,7 +46,10 @@ describe('Reports Store Mutations', () => {
name: 'StringHelper#concatenate when a is git and b is lab returns summary',
execution_time: 0.0092435,
system_output: "Failure/Error: is_expected.to eq('gitlab')",
- recent_failures: 4,
+ recent_failures: {
+ count: 4,
+ base_branch: 'master',
+ },
},
],
resolved_failures: [
diff --git a/spec/frontend/reports/store/utils_spec.js b/spec/frontend/reports/store/utils_spec.js
index 8977268115e..5249e9ffcce 100644
--- a/spec/frontend/reports/store/utils_spec.js
+++ b/spec/frontend/reports/store/utils_spec.js
@@ -188,9 +188,9 @@ describe('Reports store utils', () => {
describe('countRecentlyFailedTests', () => {
it('counts tests with more than one recent failure in a report', () => {
const report = {
- new_failures: [{ recent_failures: 2 }],
- existing_failures: [{ recent_failures: 1 }],
- resolved_failures: [{ recent_failures: 20 }, { recent_failures: 5 }],
+ new_failures: [{ recent_failures: { count: 2 } }],
+ existing_failures: [{ recent_failures: { count: 1 } }],
+ resolved_failures: [{ recent_failures: { count: 20 } }, { recent_failures: { count: 5 } }],
};
const result = utils.countRecentlyFailedTests(report);
@@ -200,14 +200,17 @@ describe('Reports store utils', () => {
it('counts tests with more than one recent failure in an array of reports', () => {
const reports = [
{
- new_failures: [{ recent_failures: 2 }],
- existing_failures: [{ recent_failures: 20 }, { recent_failures: 5 }],
- resolved_failures: [{ recent_failures: 2 }],
+ new_failures: [{ recent_failures: { count: 2 } }],
+ existing_failures: [
+ { recent_failures: { count: 20 } },
+ { recent_failures: { count: 5 } },
+ ],
+ resolved_failures: [{ recent_failures: { count: 2 } }],
},
{
- new_failures: [{ recent_failures: 8 }, { recent_failures: 14 }],
- existing_failures: [{ recent_failures: 1 }],
- resolved_failures: [{ recent_failures: 7 }, { recent_failures: 5 }],
+ new_failures: [{ recent_failures: { count: 8 } }, { recent_failures: { count: 14 } }],
+ existing_failures: [{ recent_failures: { count: 1 } }],
+ resolved_failures: [{ recent_failures: { count: 7 } }, { recent_failures: { count: 5 } }],
},
];
const result = utils.countRecentlyFailedTests(reports);
diff --git a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
index e2ccc07d0f2..48a4feca1e5 100644
--- a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
+++ b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
@@ -11,7 +11,6 @@ exports[`Repository file preview component renders file HTML 1`] = `
class="file-header-content"
>
<gl-icon-stub
- aria-hidden="true"
name="doc-text"
size="16"
/>
diff --git a/spec/frontend/search/index_spec.js b/spec/frontend/search/index_spec.js
index 8a86cc4c52a..31b5aa3686b 100644
--- a/spec/frontend/search/index_spec.js
+++ b/spec/frontend/search/index_spec.js
@@ -2,8 +2,8 @@ import { initSearchApp } from '~/search';
import createStore from '~/search/store';
jest.mock('~/search/store');
+jest.mock('~/search/topbar');
jest.mock('~/search/sidebar');
-jest.mock('~/search/group_filter');
describe('initSearchApp', () => {
let defaultLocation;
diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js
index 68fc432881a..ee509eaad8d 100644
--- a/spec/frontend/search/mock_data.js
+++ b/spec/frontend/search/mock_data.js
@@ -2,6 +2,7 @@ export const MOCK_QUERY = {
scope: 'issues',
state: 'all',
confidential: null,
+ group_id: 'test_1',
};
export const MOCK_GROUP = {
@@ -22,3 +23,25 @@ export const MOCK_GROUPS = [
id: 'test_2',
},
];
+
+export const MOCK_PROJECT = {
+ name: 'test project',
+ namespace_id: MOCK_GROUP.id,
+ nameWithNamespace: 'test group test project',
+ id: 'test_1',
+};
+
+export const MOCK_PROJECTS = [
+ {
+ name: 'test project',
+ namespace_id: MOCK_GROUP.id,
+ name_with_namespace: 'test group test project',
+ id: 'test_1',
+ },
+ {
+ name: 'test project 2',
+ namespace_id: MOCK_GROUP.id,
+ name_with_namespace: 'test group test project 2',
+ id: 'test_2',
+ },
+];
diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js
index c8ea6167399..e4536a3e136 100644
--- a/spec/frontend/search/store/actions_spec.js
+++ b/spec/frontend/search/store/actions_spec.js
@@ -1,22 +1,24 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
+import Api from '~/api';
import * as actions from '~/search/store/actions';
import * as types from '~/search/store/mutation_types';
-import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
-import state from '~/search/store/state';
+import * as urlUtils from '~/lib/utils/url_utility';
+import createState from '~/search/store/state';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
-import { MOCK_GROUPS } from '../mock_data';
+import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECT, MOCK_PROJECTS } from '../mock_data';
jest.mock('~/flash');
jest.mock('~/lib/utils/url_utility', () => ({
setUrlParams: jest.fn(),
+ joinPaths: jest.fn().mockReturnValue(''),
visitUrl: jest.fn(),
- joinPaths: jest.fn(), // For the axios specs
}));
describe('Global Search Store Actions', () => {
let mock;
+ let state;
const noCallback = () => {};
const flashCallback = () => {
@@ -25,66 +27,97 @@ describe('Global Search Store Actions', () => {
};
beforeEach(() => {
+ state = createState({ query: MOCK_QUERY });
mock = new MockAdapter(axios);
});
afterEach(() => {
+ state = null;
mock.restore();
});
describe.each`
- action | axiosMock | type | mutationCalls | callback
- ${actions.fetchGroups} | ${{ method: 'onGet', code: 200, res: MOCK_GROUPS }} | ${'success'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_SUCCESS, payload: MOCK_GROUPS }]} | ${noCallback}
- ${actions.fetchGroups} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_ERROR }]} | ${flashCallback}
- `(`axios calls`, ({ action, axiosMock, type, mutationCalls, callback }) => {
+ action | axiosMock | type | expectedMutations | callback
+ ${actions.fetchGroups} | ${{ method: 'onGet', code: 200, res: MOCK_GROUPS }} | ${'success'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_SUCCESS, payload: MOCK_GROUPS }]} | ${noCallback}
+ ${actions.fetchGroups} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_ERROR }]} | ${flashCallback}
+ ${actions.fetchProjects} | ${{ method: 'onGet', code: 200, res: MOCK_PROJECTS }} | ${'success'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_SUCCESS, payload: MOCK_PROJECTS }]} | ${noCallback}
+ ${actions.fetchProjects} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_ERROR }]} | ${flashCallback}
+ `(`axios calls`, ({ action, axiosMock, type, expectedMutations, callback }) => {
describe(action.name, () => {
describe(`on ${type}`, () => {
beforeEach(() => {
mock[axiosMock.method]().replyOnce(axiosMock.code, axiosMock.res);
});
it(`should dispatch the correct mutations`, () => {
- return testAction(action, null, state, mutationCalls, []).then(() => callback());
+ return testAction({ action, state, expectedMutations }).then(() => callback());
});
});
});
});
+ describe('getProjectsData', () => {
+ const mockCommit = () => {};
+ beforeEach(() => {
+ jest.spyOn(Api, 'groupProjects').mockResolvedValue(MOCK_PROJECTS);
+ jest.spyOn(Api, 'projects').mockResolvedValue(MOCK_PROJECT);
+ });
+
+ describe('when groupId is set', () => {
+ it('calls Api.groupProjects', () => {
+ actions.fetchProjects({ commit: mockCommit, state });
+
+ expect(Api.groupProjects).toHaveBeenCalled();
+ expect(Api.projects).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when groupId is not set', () => {
+ beforeEach(() => {
+ state = createState({ query: { group_id: null } });
+ });
+
+ it('calls Api.projects', () => {
+ actions.fetchProjects({ commit: mockCommit, state });
+
+ expect(Api.groupProjects).not.toHaveBeenCalled();
+ expect(Api.projects).toHaveBeenCalled();
+ });
+ });
+ });
+
describe('setQuery', () => {
const payload = { key: 'key1', value: 'value1' };
- it('calls the SET_QUERY mutation', done => {
- testAction(actions.setQuery, payload, state, [{ type: types.SET_QUERY, payload }], [], done);
+ it('calls the SET_QUERY mutation', () => {
+ return testAction({
+ action: actions.setQuery,
+ payload,
+ state,
+ expectedMutations: [{ type: types.SET_QUERY, payload }],
+ });
});
});
describe('applyQuery', () => {
it('calls visitUrl and setParams with the state.query', () => {
- testAction(actions.applyQuery, null, state, [], [], () => {
- expect(setUrlParams).toHaveBeenCalledWith({ ...state.query, page: null });
- expect(visitUrl).toHaveBeenCalled();
+ return testAction(actions.applyQuery, null, state, [], [], () => {
+ expect(urlUtils.setUrlParams).toHaveBeenCalledWith({ ...state.query, page: null });
+ expect(urlUtils.visitUrl).toHaveBeenCalled();
});
});
});
describe('resetQuery', () => {
it('calls visitUrl and setParams with empty values', () => {
- testAction(actions.resetQuery, null, state, [], [], () => {
- expect(setUrlParams).toHaveBeenCalledWith({
+ return testAction(actions.resetQuery, null, state, [], [], () => {
+ expect(urlUtils.setUrlParams).toHaveBeenCalledWith({
...state.query,
page: null,
state: null,
confidential: null,
});
- expect(visitUrl).toHaveBeenCalled();
+ expect(urlUtils.visitUrl).toHaveBeenCalled();
});
});
});
});
-
-describe('setQuery', () => {
- const payload = { key: 'key1', value: 'value1' };
-
- it('calls the SET_QUERY mutation', done => {
- testAction(actions.setQuery, payload, state, [{ type: types.SET_QUERY, payload }], [], done);
- });
-});
diff --git a/spec/frontend/search/store/mutations_spec.js b/spec/frontend/search/store/mutations_spec.js
index 28d9646b97e..560ed66263b 100644
--- a/spec/frontend/search/store/mutations_spec.js
+++ b/spec/frontend/search/store/mutations_spec.js
@@ -1,7 +1,7 @@
import mutations from '~/search/store/mutations';
import createState from '~/search/store/state';
import * as types from '~/search/store/mutation_types';
-import { MOCK_QUERY, MOCK_GROUPS } from '../mock_data';
+import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECTS } from '../mock_data';
describe('Global Search Store Mutations', () => {
let state;
@@ -36,6 +36,32 @@ describe('Global Search Store Mutations', () => {
});
});
+ describe('REQUEST_PROJECTS', () => {
+ it('sets fetchingProjects to true', () => {
+ mutations[types.REQUEST_PROJECTS](state);
+
+ expect(state.fetchingProjects).toBe(true);
+ });
+ });
+
+ describe('RECEIVE_PROJECTS_SUCCESS', () => {
+ it('sets fetchingProjects to false and sets projects', () => {
+ mutations[types.RECEIVE_PROJECTS_SUCCESS](state, MOCK_PROJECTS);
+
+ expect(state.fetchingProjects).toBe(false);
+ expect(state.projects).toBe(MOCK_PROJECTS);
+ });
+ });
+
+ describe('RECEIVE_PROJECTS_ERROR', () => {
+ it('sets fetchingProjects to false and clears projects', () => {
+ mutations[types.RECEIVE_PROJECTS_ERROR](state);
+
+ expect(state.fetchingProjects).toBe(false);
+ expect(state.projects).toEqual([]);
+ });
+ });
+
describe('SET_QUERY', () => {
const payload = { key: 'key1', value: 'value1' };
diff --git a/spec/frontend/search/topbar/components/group_filter_spec.js b/spec/frontend/search/topbar/components/group_filter_spec.js
new file mode 100644
index 00000000000..017808d576e
--- /dev/null
+++ b/spec/frontend/search/topbar/components/group_filter_spec.js
@@ -0,0 +1,121 @@
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { MOCK_GROUP, MOCK_QUERY } from 'jest/search/mock_data';
+import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
+import GroupFilter from '~/search/topbar/components/group_filter.vue';
+import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue';
+import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/topbar/constants';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn(),
+ setUrlParams: jest.fn(),
+}));
+
+describe('GroupFilter', () => {
+ let wrapper;
+
+ const actionSpies = {
+ fetchGroups: jest.fn(),
+ };
+
+ const defaultProps = {
+ initialData: null,
+ };
+
+ const createComponent = (initialState, props) => {
+ const store = new Vuex.Store({
+ state: {
+ query: MOCK_QUERY,
+ ...initialState,
+ },
+ actions: actionSpies,
+ });
+
+ wrapper = shallowMount(GroupFilter, {
+ localVue,
+ store,
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findSearchableDropdown = () => wrapper.find(SearchableDropdown);
+
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders SearchableDropdown always', () => {
+ expect(findSearchableDropdown().exists()).toBe(true);
+ });
+ });
+
+ describe('events', () => {
+ describe('when @search is emitted', () => {
+ const search = 'test';
+
+ beforeEach(() => {
+ createComponent();
+
+ findSearchableDropdown().vm.$emit('search', search);
+ });
+
+ it('calls fetchGroups with the search paramter', () => {
+ expect(actionSpies.fetchGroups).toHaveBeenCalledTimes(1);
+ expect(actionSpies.fetchGroups).toHaveBeenCalledWith(expect.any(Object), search);
+ });
+ });
+
+ describe('when @change is emitted', () => {
+ beforeEach(() => {
+ createComponent();
+
+ findSearchableDropdown().vm.$emit('change', MOCK_GROUP);
+ });
+
+ it('calls calls setUrlParams with group id, project id null, and visitUrl', () => {
+ expect(setUrlParams).toHaveBeenCalledWith({
+ [GROUP_DATA.queryParam]: MOCK_GROUP.id,
+ [PROJECT_DATA.queryParam]: null,
+ });
+
+ expect(visitUrl).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('computed', () => {
+ describe('selectedGroup', () => {
+ describe('when initialData is null', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('sets selectedGroup to ANY_OPTION', () => {
+ expect(wrapper.vm.selectedGroup).toBe(ANY_OPTION);
+ });
+ });
+
+ describe('when initialData is set', () => {
+ beforeEach(() => {
+ createComponent({}, { initialData: MOCK_GROUP });
+ });
+
+ it('sets selectedGroup to ANY_OPTION', () => {
+ expect(wrapper.vm.selectedGroup).toBe(MOCK_GROUP);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/search/topbar/components/project_filter_spec.js b/spec/frontend/search/topbar/components/project_filter_spec.js
new file mode 100644
index 00000000000..c1fc61d7e89
--- /dev/null
+++ b/spec/frontend/search/topbar/components/project_filter_spec.js
@@ -0,0 +1,134 @@
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { MOCK_PROJECT, MOCK_QUERY } from 'jest/search/mock_data';
+import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
+import ProjectFilter from '~/search/topbar/components/project_filter.vue';
+import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue';
+import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/topbar/constants';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn(),
+ setUrlParams: jest.fn(),
+}));
+
+describe('ProjectFilter', () => {
+ let wrapper;
+
+ const actionSpies = {
+ fetchProjects: jest.fn(),
+ };
+
+ const defaultProps = {
+ initialData: null,
+ };
+
+ const createComponent = (initialState, props) => {
+ const store = new Vuex.Store({
+ state: {
+ query: MOCK_QUERY,
+ ...initialState,
+ },
+ actions: actionSpies,
+ });
+
+ wrapper = shallowMount(ProjectFilter, {
+ localVue,
+ store,
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findSearchableDropdown = () => wrapper.find(SearchableDropdown);
+
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders SearchableDropdown always', () => {
+ expect(findSearchableDropdown().exists()).toBe(true);
+ });
+ });
+
+ describe('events', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ describe('when @search is emitted', () => {
+ const search = 'test';
+
+ beforeEach(() => {
+ findSearchableDropdown().vm.$emit('search', search);
+ });
+
+ it('calls fetchProjects with the search paramter', () => {
+ expect(actionSpies.fetchProjects).toHaveBeenCalledWith(expect.any(Object), search);
+ });
+ });
+
+ describe('when @change is emitted', () => {
+ describe('with Any', () => {
+ beforeEach(() => {
+ findSearchableDropdown().vm.$emit('change', ANY_OPTION);
+ });
+
+ it('calls setUrlParams with project id, not group id, then calls visitUrl', () => {
+ expect(setUrlParams).toHaveBeenCalledWith({
+ [PROJECT_DATA.queryParam]: ANY_OPTION.id,
+ });
+ expect(visitUrl).toHaveBeenCalled();
+ });
+ });
+
+ describe('with a Project', () => {
+ beforeEach(() => {
+ findSearchableDropdown().vm.$emit('change', MOCK_PROJECT);
+ });
+
+ it('calls setUrlParams with project id, group id, then calls visitUrl', () => {
+ expect(setUrlParams).toHaveBeenCalledWith({
+ [GROUP_DATA.queryParam]: MOCK_PROJECT.namespace_id,
+ [PROJECT_DATA.queryParam]: MOCK_PROJECT.id,
+ });
+ expect(visitUrl).toHaveBeenCalled();
+ });
+ });
+ });
+ });
+
+ describe('computed', () => {
+ describe('selectedProject', () => {
+ describe('when initialData is null', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('sets selectedProject to ANY_OPTION', () => {
+ expect(wrapper.vm.selectedProject).toBe(ANY_OPTION);
+ });
+ });
+
+ describe('when initialData is set', () => {
+ beforeEach(() => {
+ createComponent({}, { initialData: MOCK_PROJECT });
+ });
+
+ it('sets selectedProject to the initialData', () => {
+ expect(wrapper.vm.selectedProject).toBe(MOCK_PROJECT);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/search/group_filter/components/group_filter_spec.js b/spec/frontend/search/topbar/components/searchable_dropdown_spec.js
index fd3a4449f41..c4ebaabbf96 100644
--- a/spec/frontend/search/group_filter/components/group_filter_spec.js
+++ b/spec/frontend/search/topbar/components/searchable_dropdown_spec.js
@@ -1,41 +1,34 @@
import Vuex from 'vuex';
import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
-import * as urlUtils from '~/lib/utils/url_utility';
-import GroupFilter from '~/search/group_filter/components/group_filter.vue';
-import { GROUP_QUERY_PARAM, PROJECT_QUERY_PARAM, ANY_GROUP } from '~/search/group_filter/constants';
-import { MOCK_GROUPS, MOCK_GROUP, MOCK_QUERY } from '../../mock_data';
+import { MOCK_GROUPS, MOCK_GROUP, MOCK_QUERY } from 'jest/search/mock_data';
+import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue';
+import { ANY_OPTION, GROUP_DATA } from '~/search/topbar/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
-jest.mock('~/flash');
-jest.mock('~/lib/utils/url_utility', () => ({
- visitUrl: jest.fn(),
- setUrlParams: jest.fn(),
-}));
-
-describe('Global Search Group Filter', () => {
+describe('Global Search Searchable Dropdown', () => {
let wrapper;
- const actionSpies = {
- fetchGroups: jest.fn(),
- };
-
const defaultProps = {
- initialGroup: null,
+ headerText: GROUP_DATA.headerText,
+ selectedDisplayValue: GROUP_DATA.selectedDisplayValue,
+ itemsDisplayValue: GROUP_DATA.itemsDisplayValue,
+ loading: false,
+ selectedItem: ANY_OPTION,
+ items: [],
};
- const createComponent = (initialState, props = {}, mountFn = shallowMount) => {
+ const createComponent = (initialState, props, mountFn = shallowMount) => {
const store = new Vuex.Store({
state: {
query: MOCK_QUERY,
...initialState,
},
- actions: actionSpies,
});
- wrapper = mountFn(GroupFilter, {
+ wrapper = mountFn(SearchableDropdown, {
localVue,
store,
propsData: {
@@ -78,22 +71,22 @@ describe('Global Search Group Filter', () => {
});
describe('onSearch', () => {
- const groupSearch = 'test search';
+ const search = 'test search';
beforeEach(() => {
- findGlDropdownSearch().vm.$emit('input', groupSearch);
+ findGlDropdownSearch().vm.$emit('input', search);
});
- it('calls fetchGroups when input event is fired from GlSearchBoxByType', () => {
- expect(actionSpies.fetchGroups).toHaveBeenCalledWith(expect.any(Object), groupSearch);
+ it('$emits @search when input event is fired from GlSearchBoxByType', () => {
+ expect(wrapper.emitted('search')[0]).toEqual([search]);
});
});
});
describe('findDropdownItems', () => {
- describe('when fetchingGroups is false', () => {
+ describe('when loading is false', () => {
beforeEach(() => {
- createComponent({ groups: MOCK_GROUPS });
+ createComponent({}, { items: MOCK_GROUPS });
});
it('does not render loader', () => {
@@ -101,14 +94,14 @@ describe('Global Search Group Filter', () => {
});
it('renders an instance for each namespace', () => {
- const groupsIncludingAny = ['Any'].concat(MOCK_GROUPS.map(n => n.full_name));
- expect(findDropdownItemsText()).toStrictEqual(groupsIncludingAny);
+ const resultsIncludeAny = ['Any'].concat(MOCK_GROUPS.map(n => n.full_name));
+ expect(findDropdownItemsText()).toStrictEqual(resultsIncludeAny);
});
});
- describe('when fetchingGroups is true', () => {
+ describe('when loading is true', () => {
beforeEach(() => {
- createComponent({ fetchingGroups: true, groups: MOCK_GROUPS });
+ createComponent({}, { loading: true, items: MOCK_GROUPS });
});
it('does render loader', () => {
@@ -119,26 +112,36 @@ describe('Global Search Group Filter', () => {
expect(findDropdownItemsText()).toStrictEqual(['Any']);
});
});
+
+ describe('when item is selected', () => {
+ beforeEach(() => {
+ createComponent({}, { items: MOCK_GROUPS, selectedItem: MOCK_GROUPS[0] });
+ });
+
+ it('marks the dropdown as checked', () => {
+ expect(findFirstGroupDropdownItem().attributes('ischecked')).toBe('true');
+ });
+ });
});
describe('Dropdown Text', () => {
- describe('when initialGroup is null', () => {
+ describe('when selectedItem is any', () => {
beforeEach(() => {
createComponent({}, {}, mount);
});
it('sets dropdown text to Any', () => {
- expect(findDropdownText().text()).toBe(ANY_GROUP.name);
+ expect(findDropdownText().text()).toBe(ANY_OPTION.name);
});
});
- describe('initialGroup is set', () => {
+ describe('selectedItem is set', () => {
beforeEach(() => {
- createComponent({}, { initialGroup: MOCK_GROUP }, mount);
+ createComponent({}, { selectedItem: MOCK_GROUP }, mount);
});
- it('sets dropdown text to group name', () => {
- expect(findDropdownText().text()).toBe(MOCK_GROUP.name);
+ it('sets dropdown text to the selectedItem selectedDisplayValue', () => {
+ expect(findDropdownText().text()).toBe(MOCK_GROUP[GROUP_DATA.selectedDisplayValue]);
});
});
});
@@ -146,27 +149,19 @@ describe('Global Search Group Filter', () => {
describe('actions', () => {
beforeEach(() => {
- createComponent({ groups: MOCK_GROUPS });
+ createComponent({}, { items: MOCK_GROUPS });
});
- it('clicking "Any" dropdown item calls setUrlParams with group id null, project id null,and visitUrl', () => {
+ it('clicking "Any" dropdown item $emits @change with ANY_OPTION', () => {
findAnyDropdownItem().vm.$emit('click');
- expect(urlUtils.setUrlParams).toHaveBeenCalledWith({
- [GROUP_QUERY_PARAM]: ANY_GROUP.id,
- [PROJECT_QUERY_PARAM]: null,
- });
- expect(urlUtils.visitUrl).toHaveBeenCalled();
+ expect(wrapper.emitted('change')[0]).toEqual([ANY_OPTION]);
});
- it('clicking group dropdown item calls setUrlParams with group id, project id null, and visitUrl', () => {
+ it('clicking result dropdown item $emits @change with result', () => {
findFirstGroupDropdownItem().vm.$emit('click');
- expect(urlUtils.setUrlParams).toHaveBeenCalledWith({
- [GROUP_QUERY_PARAM]: MOCK_GROUPS[0].id,
- [PROJECT_QUERY_PARAM]: null,
- });
- expect(urlUtils.visitUrl).toHaveBeenCalled();
+ expect(wrapper.emitted('change')[0]).toEqual([MOCK_GROUPS[0]]);
});
});
});
diff --git a/spec/frontend/search_spec.js b/spec/frontend/search_spec.js
index cbbc2df6c78..d234a7fccb9 100644
--- a/spec/frontend/search_spec.js
+++ b/spec/frontend/search_spec.js
@@ -1,6 +1,4 @@
-import $ from 'jquery';
import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result';
-import Api from '~/api';
import Search from '~/pages/search/show/search';
jest.mock('~/api');
@@ -8,13 +6,6 @@ jest.mock('ee_else_ce/search/highlight_blob_search_result');
describe('Search', () => {
const fixturePath = 'search/show.html';
- const searchTerm = 'some search';
- const fillDropdownInput = dropdownSelector => {
- const dropdownElement = document.querySelector(dropdownSelector).parentNode;
- const inputElement = dropdownElement.querySelector('.dropdown-input-field');
- inputElement.value = searchTerm;
- return inputElement;
- };
preloadFixtures(fixturePath);
@@ -29,20 +20,4 @@ describe('Search', () => {
expect(setHighlightClass).toHaveBeenCalled();
});
});
-
- describe('dropdown behavior', () => {
- beforeEach(() => {
- loadFixtures(fixturePath);
- new Search(); // eslint-disable-line no-new
- });
-
- it('requests projects from backend when filtering', () => {
- jest.spyOn(Api, 'projects').mockImplementation(term => {
- expect(term).toBe(searchTerm);
- });
- const inputElement = fillDropdownInput('.js-search-project-dropdown');
-
- $(inputElement).trigger('input');
- });
- });
});
diff --git a/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap b/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap
index 11ab1ca3aaa..2367667544d 100644
--- a/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap
+++ b/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap
@@ -10,7 +10,6 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
title="Not confidential"
>
<gl-icon-stub
- aria-hidden="true"
name="eye"
size="16"
/>
@@ -35,7 +34,6 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
data-testid="not-confidential"
>
<gl-icon-stub
- aria-hidden="true"
class="sidebar-item-icon inline"
name="eye"
size="16"
@@ -58,7 +56,6 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
title="Not confidential"
>
<gl-icon-stub
- aria-hidden="true"
name="eye"
size="16"
/>
@@ -91,7 +88,6 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
data-testid="not-confidential"
>
<gl-icon-stub
- aria-hidden="true"
class="sidebar-item-icon inline"
name="eye"
size="16"
@@ -114,7 +110,6 @@ exports[`Confidential Issue Sidebar Block renders for confidential = true and is
title="Confidential"
>
<gl-icon-stub
- aria-hidden="true"
name="eye-slash"
size="16"
/>
@@ -138,7 +133,6 @@ exports[`Confidential Issue Sidebar Block renders for confidential = true and is
class="value sidebar-item-value hide-collapsed"
>
<gl-icon-stub
- aria-hidden="true"
class="sidebar-item-icon inline is-active"
name="eye-slash"
size="16"
@@ -161,7 +155,6 @@ exports[`Confidential Issue Sidebar Block renders for confidential = true and is
title="Confidential"
>
<gl-icon-stub
- aria-hidden="true"
name="eye-slash"
size="16"
/>
@@ -193,7 +186,6 @@ exports[`Confidential Issue Sidebar Block renders for confidential = true and is
class="value sidebar-item-value hide-collapsed"
>
<gl-icon-stub
- aria-hidden="true"
class="sidebar-item-icon inline is-active"
name="eye-slash"
size="16"
diff --git a/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap b/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap
index 6640c0844e2..e295c587d70 100644
--- a/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap
+++ b/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap
@@ -4,13 +4,8 @@ exports[`SidebarTodo template renders component container element with proper da
<button
aria-label="Mark as done"
class="btn btn-default btn-todo issuable-header-btn float-right"
- data-boundary="viewport"
- data-container="body"
data-issuable-id="1"
data-issuable-type="epic"
- data-original-title=""
- data-placement="left"
- title=""
type="button"
>
<gl-icon-stub
diff --git a/spec/frontend/sidebar/issuable_assignees_spec.js b/spec/frontend/sidebar/issuable_assignees_spec.js
index 076616de040..af4dc315aad 100644
--- a/spec/frontend/sidebar/issuable_assignees_spec.js
+++ b/spec/frontend/sidebar/issuable_assignees_spec.js
@@ -26,8 +26,8 @@ describe('IssuableAssignees', () => {
createComponent();
});
- it('renders "None"', () => {
- expect(findEmptyAssignee().text()).toBe('None');
+ it('renders "None - assign yourself"', () => {
+ expect(findEmptyAssignee().text()).toBe('None - assign yourself');
});
});
@@ -38,4 +38,12 @@ describe('IssuableAssignees', () => {
expect(findUncollapsedAssigneeList().exists()).toBe(true);
});
});
+
+ describe('when clicking "assign yourself"', () => {
+ it('emits "assign-self"', () => {
+ createComponent();
+ wrapper.find('[data-testid="assign-yourself"]').vm.$emit('click');
+ expect(wrapper.emitted('assign-self')).toHaveLength(1);
+ });
+ });
});
diff --git a/spec/frontend/sidebar/sidebar_labels_spec.js b/spec/frontend/sidebar/sidebar_labels_spec.js
index 36d1e129b6a..ab08a1e65e2 100644
--- a/spec/frontend/sidebar/sidebar_labels_spec.js
+++ b/spec/frontend/sidebar/sidebar_labels_spec.js
@@ -3,7 +3,7 @@ import {
mockLabels,
mockRegularLabel,
} from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
-import updateIssueLabelsMutation from '~/boards/queries/issue_set_labels.mutation.graphql';
+import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
import { MutationOperationMode } from '~/graphql_shared/utils';
import { IssuableType } from '~/issue_show/constants';
import SidebarLabels from '~/sidebar/components/labels/sidebar_labels.vue';
diff --git a/spec/frontend/static_site_editor/components/edit_area_spec.js b/spec/frontend/static_site_editor/components/edit_area_spec.js
index 247aff57c1a..ed33f93ec51 100644
--- a/spec/frontend/static_site_editor/components/edit_area_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_area_spec.js
@@ -250,4 +250,17 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
expect(wrapper.emitted('submit').length).toBe(1);
});
});
+
+ describe('when RichContentEditor component triggers load event', () => {
+ it('stores formatted markdown provided in the event data', () => {
+ const data = { formattedMarkdown: 'formatted markdown' };
+
+ findRichContentEditor().vm.$emit('load', data);
+
+ // We can access the formatted markdown when submitting changes
+ findPublishToolbar().vm.$emit('submit');
+
+ expect(wrapper.emitted('submit')[0][0]).toMatchObject(data);
+ });
+ });
});
diff --git a/spec/frontend/static_site_editor/pages/home_spec.js b/spec/frontend/static_site_editor/pages/home_spec.js
index d0b72ad0cf0..3e488a950dc 100644
--- a/spec/frontend/static_site_editor/pages/home_spec.js
+++ b/spec/frontend/static_site_editor/pages/home_spec.js
@@ -235,6 +235,7 @@ describe('static_site_editor/pages/home', () => {
describe('when submitting changes succeeds', () => {
const newContent = `new ${content}`;
+ const formattedMarkdown = `formatted ${content}`;
beforeEach(() => {
mutateMock.mockResolvedValueOnce(hasSubmittedChangesMutationPayload).mockResolvedValueOnce({
@@ -243,7 +244,12 @@ describe('static_site_editor/pages/home', () => {
},
});
- buildWrapper({ content: newContent, images });
+ buildWrapper();
+
+ findEditMetaModal().vm.show = jest.fn();
+
+ findEditArea().vm.$emit('submit', { content: newContent, images, formattedMarkdown });
+
findEditMetaModal().vm.$emit('primary', mergeRequestMeta);
return wrapper.vm.$nextTick();
@@ -266,6 +272,7 @@ describe('static_site_editor/pages/home', () => {
variables: {
input: {
content: newContent,
+ formattedMarkdown,
project,
sourcePath,
username,
diff --git a/spec/frontend/static_site_editor/services/submit_content_changes_spec.js b/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
index 5018da7300b..6c2bff6740a 100644
--- a/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
+++ b/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
@@ -9,6 +9,10 @@ import {
SUBMIT_CHANGES_MERGE_REQUEST_ERROR,
TRACKING_ACTION_CREATE_COMMIT,
TRACKING_ACTION_CREATE_MERGE_REQUEST,
+ USAGE_PING_TRACKING_ACTION_CREATE_COMMIT,
+ USAGE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST,
+ DEFAULT_FORMATTING_CHANGES_COMMIT_MESSAGE,
+ DEFAULT_FORMATTING_CHANGES_COMMIT_DESCRIPTION,
} from '~/static_site_editor/constants';
import generateBranchName from '~/static_site_editor/services/generate_branch_name';
import submitContentChanges from '~/static_site_editor/services/submit_content_changes';
@@ -79,6 +83,36 @@ describe('submitContentChanges', () => {
);
});
+ describe('committing markdown formatting changes', () => {
+ const formattedMarkdown = `formatted ${content}`;
+ const commitPayload = {
+ branch,
+ commit_message: `${DEFAULT_FORMATTING_CHANGES_COMMIT_MESSAGE}\n\n${DEFAULT_FORMATTING_CHANGES_COMMIT_DESCRIPTION}`,
+ actions: [
+ {
+ action: 'update',
+ file_path: sourcePath,
+ content: formattedMarkdown,
+ },
+ ],
+ };
+
+ it('commits markdown formatting changes in a separate commit', () => {
+ return submitContentChanges(buildPayload({ formattedMarkdown })).then(() => {
+ expect(Api.commitMultiple).toHaveBeenCalledWith(projectId, commitPayload);
+ });
+ });
+
+ it('does not commit markdown formatting changes when there are none', () => {
+ return submitContentChanges(buildPayload()).then(() => {
+ expect(Api.commitMultiple.mock.calls).toHaveLength(1);
+ expect(Api.commitMultiple.mock.calls[0][1]).not.toMatchObject({
+ actions: commitPayload.actions,
+ });
+ });
+ });
+ });
+
it('commits the content changes to the branch when creating branch succeeds', () => {
return submitContentChanges(buildPayload()).then(() => {
expect(Api.commitMultiple).toHaveBeenCalledWith(projectId, {
@@ -201,4 +235,26 @@ describe('submitContentChanges', () => {
);
});
});
+
+ describe('sends the correct Usage Ping tracking event', () => {
+ beforeEach(() => {
+ jest.spyOn(Api, 'trackRedisCounterEvent').mockResolvedValue({ data: '' });
+ });
+
+ it('for commiting changes', () => {
+ return submitContentChanges(buildPayload()).then(() => {
+ expect(Api.trackRedisCounterEvent).toHaveBeenCalledWith(
+ USAGE_PING_TRACKING_ACTION_CREATE_COMMIT,
+ );
+ });
+ });
+
+ it('for creating a merge request', () => {
+ return submitContentChanges(buildPayload()).then(() => {
+ expect(Api.trackRedisCounterEvent).toHaveBeenCalledWith(
+ USAGE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST,
+ );
+ });
+ });
+ });
});
diff --git a/spec/frontend/terraform/components/states_table_actions_spec.js b/spec/frontend/terraform/components/states_table_actions_spec.js
new file mode 100644
index 00000000000..264f4b7939a
--- /dev/null
+++ b/spec/frontend/terraform/components/states_table_actions_spec.js
@@ -0,0 +1,189 @@
+import { GlDropdown, GlModal, GlSprintf } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
+import VueApollo from 'vue-apollo';
+import StateActions from '~/terraform/components/states_table_actions.vue';
+import lockStateMutation from '~/terraform/graphql/mutations/lock_state.mutation.graphql';
+import removeStateMutation from '~/terraform/graphql/mutations/remove_state.mutation.graphql';
+import unlockStateMutation from '~/terraform/graphql/mutations/unlock_state.mutation.graphql';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('StatesTableActions', () => {
+ let lockResponse;
+ let removeResponse;
+ let unlockResponse;
+ let wrapper;
+
+ const defaultProps = {
+ state: {
+ id: 'gid/1',
+ name: 'state-1',
+ latestVersion: { downloadPath: '/path' },
+ lockedAt: '2020-10-13T00:00:00Z',
+ },
+ };
+
+ const createMockApolloProvider = () => {
+ lockResponse = jest.fn().mockResolvedValue({ data: { terraformStateLock: { errors: [] } } });
+
+ removeResponse = jest
+ .fn()
+ .mockResolvedValue({ data: { terraformStateDelete: { errors: [] } } });
+
+ unlockResponse = jest
+ .fn()
+ .mockResolvedValue({ data: { terraformStateUnlock: { errors: [] } } });
+
+ return createMockApollo([
+ [lockStateMutation, lockResponse],
+ [removeStateMutation, removeResponse],
+ [unlockStateMutation, unlockResponse],
+ ]);
+ };
+
+ const createComponent = (propsData = defaultProps) => {
+ const apolloProvider = createMockApolloProvider();
+
+ wrapper = shallowMount(StateActions, {
+ apolloProvider,
+ localVue,
+ propsData,
+ stubs: { GlDropdown, GlModal, GlSprintf },
+ });
+
+ return wrapper.vm.$nextTick();
+ };
+
+ const findLockBtn = () => wrapper.find('[data-testid="terraform-state-lock"]');
+ const findUnlockBtn = () => wrapper.find('[data-testid="terraform-state-unlock"]');
+ const findDownloadBtn = () => wrapper.find('[data-testid="terraform-state-download"]');
+ const findRemoveBtn = () => wrapper.find('[data-testid="terraform-state-remove"]');
+ const findRemoveModal = () => wrapper.find(GlModal);
+
+ beforeEach(() => {
+ return createComponent();
+ });
+
+ afterEach(() => {
+ lockResponse = null;
+ removeResponse = null;
+ unlockResponse = null;
+ wrapper.destroy();
+ });
+
+ describe('download button', () => {
+ it('displays a download button', () => {
+ expect(findDownloadBtn().text()).toBe('Download JSON');
+ });
+
+ describe('when state does not have a latestVersion', () => {
+ beforeEach(() => {
+ return createComponent({
+ state: {
+ id: 'gid/1',
+ name: 'state-1',
+ latestVersion: null,
+ },
+ });
+ });
+
+ it('does not display a download button', () => {
+ expect(findDownloadBtn().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('unlock button', () => {
+ it('displays an unlock button', () => {
+ expect(findUnlockBtn().text()).toBe('Unlock');
+ expect(findLockBtn().exists()).toBe(false);
+ });
+
+ describe('when clicking the unlock button', () => {
+ beforeEach(() => {
+ findUnlockBtn().vm.$emit('click');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('calls the unlock mutation', () => {
+ expect(unlockResponse).toHaveBeenCalledWith({
+ stateID: defaultProps.state.id,
+ });
+ });
+ });
+ });
+
+ describe('lock button', () => {
+ const unlockedProps = {
+ state: {
+ id: 'gid/2',
+ name: 'state-2',
+ latestVersion: null,
+ lockedAt: null,
+ },
+ };
+
+ beforeEach(() => {
+ return createComponent(unlockedProps);
+ });
+
+ it('displays a lock button', () => {
+ expect(findLockBtn().text()).toBe('Lock');
+ expect(findUnlockBtn().exists()).toBe(false);
+ });
+
+ describe('when clicking the lock button', () => {
+ beforeEach(() => {
+ findLockBtn().vm.$emit('click');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('calls the lock mutation', () => {
+ expect(lockResponse).toHaveBeenCalledWith({
+ stateID: unlockedProps.state.id,
+ });
+ });
+ });
+ });
+
+ describe('remove button', () => {
+ it('displays a remove button', () => {
+ expect(findRemoveBtn().text()).toBe(StateActions.i18n.remove);
+ });
+
+ describe('when clicking the remove button', () => {
+ beforeEach(() => {
+ findRemoveBtn().vm.$emit('click');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('displays a remove modal', () => {
+ expect(findRemoveModal().text()).toContain(
+ `You are about to remove the State file ${defaultProps.state.name}`,
+ );
+ });
+
+ describe('when submitting the remove modal', () => {
+ it('does not call the remove mutation when state name is missing', async () => {
+ findRemoveModal().vm.$emit('ok');
+ await wrapper.vm.$nextTick();
+
+ expect(removeResponse).not.toHaveBeenCalledWith();
+ });
+
+ it('calls the remove mutation when state name is present', async () => {
+ await wrapper.setData({ removeConfirmText: defaultProps.state.name });
+
+ findRemoveModal().vm.$emit('ok');
+ await wrapper.vm.$nextTick();
+
+ expect(removeResponse).toHaveBeenCalledWith({
+ stateID: defaultProps.state.id,
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/terraform/components/states_table_spec.js b/spec/frontend/terraform/components/states_table_spec.js
index 7a8cb19971e..f2b7bc00e5b 100644
--- a/spec/frontend/terraform/components/states_table_spec.js
+++ b/spec/frontend/terraform/components/states_table_spec.js
@@ -1,13 +1,14 @@
import { GlIcon, GlTooltip } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date';
+import StateActions from '~/terraform/components/states_table_actions.vue';
import StatesTable from '~/terraform/components/states_table.vue';
describe('StatesTable', () => {
let wrapper;
useFakeDate([2020, 10, 15]);
- const propsData = {
+ const defaultProps = {
states: [
{
name: 'state-1',
@@ -37,6 +38,19 @@ describe('StatesTable', () => {
createdByUser: {
name: 'user-3',
},
+ job: {
+ detailedStatus: {
+ detailsPath: '/job-path-3',
+ group: 'failed',
+ icon: 'status_failed',
+ label: 'failed',
+ text: 'failed',
+ },
+ pipeline: {
+ id: 'gid://gitlab/Ci::Pipeline/3',
+ path: '/pipeline-path-3',
+ },
+ },
},
},
{
@@ -47,14 +61,33 @@ describe('StatesTable', () => {
latestVersion: {
updatedAt: '2020-10-09T00:00:00Z',
createdByUser: null,
+ job: {
+ detailedStatus: {
+ detailsPath: '/job-path-4',
+ group: 'passed',
+ icon: 'status_success',
+ label: 'passed',
+ text: 'passed',
+ },
+ pipeline: {
+ id: 'gid://gitlab/Ci::Pipeline/4',
+ path: '/pipeline-path-4',
+ },
+ },
},
},
],
};
- beforeEach(() => {
+ const createComponent = (propsData = defaultProps) => {
wrapper = mount(StatesTable, { propsData });
return wrapper.vm.$nextTick();
+ };
+
+ const findActions = () => wrapper.findAll(StateActions);
+
+ beforeEach(() => {
+ return createComponent();
});
afterEach(() => {
@@ -99,4 +132,38 @@ describe('StatesTable', () => {
expect(state.text()).toMatchInterpolatedText(updateTime);
});
+
+ it.each`
+ pipelineText | toolTipAdded | lineNumber
+ ${''} | ${false} | ${0}
+ ${''} | ${false} | ${1}
+ ${'#3 failed Job status'} | ${true} | ${2}
+ ${'#4 passed Job status'} | ${true} | ${3}
+ `(
+ 'displays the pipeline information for line "$lineNumber"',
+ ({ pipelineText, toolTipAdded, lineNumber }) => {
+ const states = wrapper.findAll('[data-testid="terraform-states-table-pipeline"]');
+ const state = states.at(lineNumber);
+
+ expect(state.find(GlTooltip).exists()).toBe(toolTipAdded);
+ expect(state.text()).toMatchInterpolatedText(pipelineText);
+ },
+ );
+
+ it('displays no actions dropdown', () => {
+ expect(findActions().length).toEqual(0);
+ });
+
+ describe('when user is a terraform administrator', () => {
+ beforeEach(() => {
+ return createComponent({
+ terraformAdmin: true,
+ ...defaultProps,
+ });
+ });
+
+ it('displays an actions dropdown for each state', () => {
+ expect(findActions().length).toEqual(defaultProps.states.length);
+ });
+ });
});
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index eebec7de9d4..2a3eddf7b4e 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -25,7 +25,7 @@ afterEach(() =>
}),
);
-initializeTestTimeout(process.env.CI ? 6000 : 500);
+initializeTestTimeout(process.env.CI ? 6000 : 5000);
Vue.config.devtools = false;
Vue.config.productionTip = false;
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
index 266c906ba60..f9b6ac721d2 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
@@ -164,9 +164,7 @@ describe('MRWidgetHeader', () => {
it('renders checkout branch button with modal trigger', () => {
const button = vm.$el.querySelector('.js-check-out-branch');
- expect(button.textContent.trim()).toEqual('Check out branch');
- expect(button.getAttribute('data-target')).toEqual('#modal_merge_info');
- expect(button.getAttribute('data-toggle')).toEqual('modal');
+ expect(button.textContent.trim()).toBe('Check out branch');
});
it('renders web ide button', () => {
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_merge_help_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_merge_help_spec.js
index 00e79a22485..53a74bf7456 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_merge_help_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_merge_help_spec.js
@@ -1,69 +1,45 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import mergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help.vue';
+import { shallowMount } from '@vue/test-utils';
+import MergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help.vue';
describe('MRWidgetMergeHelp', () => {
- let vm;
- let Component;
+ let wrapper;
- beforeEach(() => {
- Component = Vue.extend(mergeHelpComponent);
- });
+ const createComponent = ({ props = {} } = {}) => {
+ wrapper = shallowMount(MergeHelpComponent, {
+ propsData: {
+ missingBranch: 'this-is-not-the-branch-you-are-looking-for',
+ ...props,
+ },
+ });
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
describe('with missing branch', () => {
beforeEach(() => {
- vm = mountComponent(Component, {
- missingBranch: 'this-is-not-the-branch-you-are-looking-for',
- });
+ createComponent();
});
it('renders missing branch information', () => {
- expect(
- vm.$el.textContent
- .trim()
- .replace(/[\r\n]+/g, ' ')
- .replace(/\s\s+/g, ' '),
- ).toEqual(
- 'If the this-is-not-the-branch-you-are-looking-for branch exists in your local repository, you can merge this merge request manually using the command line',
- );
- });
-
- it('renders button to open help modal', () => {
- expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-target')).toEqual(
- '#modal_merge_info',
- );
-
- expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-toggle')).toEqual(
- 'modal',
+ expect(wrapper.find('.mr-widget-help').text()).toContain(
+ 'If the this-is-not-the-branch-you-are-looking-for branch exists in your local repository',
);
});
});
describe('without missing branch', () => {
beforeEach(() => {
- vm = mountComponent(Component);
+ createComponent({
+ props: { missingBranch: '' },
+ });
});
it('renders information about how to merge manually', () => {
- expect(
- vm.$el.textContent
- .trim()
- .replace(/[\r\n]+/g, ' ')
- .replace(/\s\s+/g, ' '),
- ).toEqual('You can merge this merge request manually using the command line');
- });
-
- it('renders element to open a modal', () => {
- expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-target')).toEqual(
- '#modal_merge_info',
- );
-
- expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-toggle')).toEqual(
- 'modal',
+ expect(wrapper.find('.mr-widget-help').text()).toContain(
+ 'You can merge this merge request manually',
);
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
index 19f8a67d066..ad21e6e6f4f 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
@@ -6,6 +6,7 @@ import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_
describe('MRWidgetConflicts', () => {
let vm;
+ let mergeRequestWidgetGraphql = null;
const path = '/conflicts';
function createComponent(propsData = {}) {
@@ -13,7 +14,35 @@ describe('MRWidgetConflicts', () => {
vm = shallowMount(localVue.extend(ConflictsComponent), {
propsData,
+ provide: {
+ glFeatures: {
+ mergeRequestWidgetGraphql,
+ },
+ },
+ mocks: {
+ $apollo: {
+ queries: {
+ userPermissions: { loading: false },
+ stateData: { loading: false },
+ },
+ },
+ },
});
+
+ if (mergeRequestWidgetGraphql) {
+ vm.setData({
+ userPermissions: {
+ canMerge: propsData.mr.canMerge,
+ pushToSourceBranch: propsData.mr.canPushToSourceBranch,
+ },
+ stateData: {
+ shouldBeRebased: propsData.mr.shouldBeRebased,
+ sourceBranchProtected: propsData.mr.sourceBranchProtected,
+ },
+ });
+ }
+
+ return vm.vm.$nextTick();
}
beforeEach(() => {
@@ -21,206 +50,215 @@ describe('MRWidgetConflicts', () => {
});
afterEach(() => {
+ mergeRequestWidgetGraphql = null;
vm.destroy();
});
- // There are two permissions we need to consider:
- //
- // 1. Is the user allowed to merge to the target branch?
- // 2. Is the user allowed to push to the source branch?
- //
- // This yields 4 possible permutations that we need to test, and
- // we test them below. A user who can push to the source
- // branch should be allowed to resolve conflicts. This is
- // consistent with what the backend does.
- describe('when allowed to merge but not allowed to push to source branch', () => {
- beforeEach(() => {
- createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: false,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
- },
+ [false, true].forEach(featureEnabled => {
+ describe(`with GraphQL feature flag ${featureEnabled ? 'enabled' : 'disabled'}`, () => {
+ beforeEach(() => {
+ mergeRequestWidgetGraphql = featureEnabled;
});
- });
-
- it('should tell you about conflicts without bothering other people', () => {
- expect(vm.text()).toContain('There are merge conflicts');
- expect(vm.text()).not.toContain('ask someone with write access');
- });
-
- it('should not allow you to resolve the conflicts', () => {
- expect(vm.text()).not.toContain('Resolve conflicts');
- });
-
- it('should have merge buttons', () => {
- const mergeLocallyButton = vm.find('.js-merge-locally-button');
-
- expect(mergeLocallyButton.text()).toContain('Merge locally');
- });
- });
- describe('when not allowed to merge but allowed to push to source branch', () => {
- beforeEach(() => {
- createComponent({
- mr: {
- canMerge: false,
- canPushToSourceBranch: true,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
- },
- });
- });
-
- it('should tell you about conflicts', () => {
- expect(vm.text()).toContain('There are merge conflicts');
- expect(vm.text()).toContain('ask someone with write access');
- });
-
- it('should allow you to resolve the conflicts', () => {
- const resolveButton = vm.find('.js-resolve-conflicts-button');
-
- expect(resolveButton.text()).toContain('Resolve conflicts');
- expect(resolveButton.attributes('href')).toEqual(path);
- });
-
- it('should not have merge buttons', () => {
- expect(vm.text()).not.toContain('Merge locally');
- });
- });
-
- describe('when allowed to merge and push to source branch', () => {
- beforeEach(() => {
- createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: true,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
- },
+ // There are two permissions we need to consider:
+ //
+ // 1. Is the user allowed to merge to the target branch?
+ // 2. Is the user allowed to push to the source branch?
+ //
+ // This yields 4 possible permutations that we need to test, and
+ // we test them below. A user who can push to the source
+ // branch should be allowed to resolve conflicts. This is
+ // consistent with what the backend does.
+ describe('when allowed to merge but not allowed to push to source branch', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: false,
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('should tell you about conflicts without bothering other people', () => {
+ expect(vm.text()).toContain('There are merge conflicts');
+ expect(vm.text()).not.toContain('ask someone with write access');
+ });
+
+ it('should not allow you to resolve the conflicts', () => {
+ expect(vm.text()).not.toContain('Resolve conflicts');
+ });
+
+ it('should have merge buttons', () => {
+ const mergeLocallyButton = vm.find('.js-merge-locally-button');
+
+ expect(mergeLocallyButton.text()).toContain('Merge locally');
+ });
});
- });
-
- it('should tell you about conflicts without bothering other people', () => {
- expect(vm.text()).toContain('There are merge conflicts');
- expect(vm.text()).not.toContain('ask someone with write access');
- });
- it('should allow you to resolve the conflicts', () => {
- const resolveButton = vm.find('.js-resolve-conflicts-button');
-
- expect(resolveButton.text()).toContain('Resolve conflicts');
- expect(resolveButton.attributes('href')).toEqual(path);
- });
-
- it('should have merge buttons', () => {
- const mergeLocallyButton = vm.find('.js-merge-locally-button');
-
- expect(mergeLocallyButton.text()).toContain('Merge locally');
- });
- });
-
- describe('when user does not have permission to push to source branch', () => {
- it('should show proper message', () => {
- createComponent({
- mr: {
- canMerge: false,
- canPushToSourceBranch: false,
- conflictsDocsPath: '',
- },
+ describe('when not allowed to merge but allowed to push to source branch', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: false,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('should tell you about conflicts', () => {
+ expect(vm.text()).toContain('There are merge conflicts');
+ expect(vm.text()).toContain('ask someone with write access');
+ });
+
+ it('should allow you to resolve the conflicts', () => {
+ const resolveButton = vm.find('.js-resolve-conflicts-button');
+
+ expect(resolveButton.text()).toContain('Resolve conflicts');
+ expect(resolveButton.attributes('href')).toEqual(path);
+ });
+
+ it('should not have merge buttons', () => {
+ expect(vm.text()).not.toContain('Merge locally');
+ });
});
- expect(
- vm
- .text()
- .trim()
- .replace(/\s\s+/g, ' '),
- ).toContain('ask someone with write access');
- });
-
- it('should not have action buttons', () => {
- createComponent({
- mr: {
- canMerge: false,
- canPushToSourceBranch: false,
- conflictsDocsPath: '',
- },
+ describe('when allowed to merge and push to source branch', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('should tell you about conflicts without bothering other people', () => {
+ expect(vm.text()).toContain('There are merge conflicts');
+ expect(vm.text()).not.toContain('ask someone with write access');
+ });
+
+ it('should allow you to resolve the conflicts', () => {
+ const resolveButton = vm.find('.js-resolve-conflicts-button');
+
+ expect(resolveButton.text()).toContain('Resolve conflicts');
+ expect(resolveButton.attributes('href')).toEqual(path);
+ });
+
+ it('should have merge buttons', () => {
+ const mergeLocallyButton = vm.find('.js-merge-locally-button');
+
+ expect(mergeLocallyButton.text()).toContain('Merge locally');
+ });
});
- expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
- expect(vm.find('.js-merge-locally-button').exists()).toBe(false);
- });
-
- it('should not have resolve button when no conflict resolution path', () => {
- createComponent({
- mr: {
- canMerge: true,
- conflictResolutionPath: null,
- conflictsDocsPath: '',
- },
+ describe('when user does not have permission to push to source branch', () => {
+ it('should show proper message', async () => {
+ await createComponent({
+ mr: {
+ canMerge: false,
+ canPushToSourceBranch: false,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(
+ vm
+ .text()
+ .trim()
+ .replace(/\s\s+/g, ' '),
+ ).toContain('ask someone with write access');
+ });
+
+ it('should not have action buttons', async () => {
+ await createComponent({
+ mr: {
+ canMerge: false,
+ canPushToSourceBranch: false,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
+ expect(vm.find('.js-merge-locally-button').exists()).toBe(false);
+ });
+
+ it('should not have resolve button when no conflict resolution path', async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: null,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
+ });
});
- expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
- });
- });
-
- describe('when fast-forward or semi-linear merge enabled', () => {
- it('should tell you to rebase locally', () => {
- createComponent({
- mr: {
- shouldBeRebased: true,
- conflictsDocsPath: '',
- },
+ describe('when fast-forward or semi-linear merge enabled', () => {
+ it('should tell you to rebase locally', async () => {
+ await createComponent({
+ mr: {
+ shouldBeRebased: true,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(removeBreakLine(vm.text()).trim()).toContain(
+ 'Fast-forward merge is not possible. To merge this request, first rebase locally.',
+ );
+ });
});
- expect(removeBreakLine(vm.text()).trim()).toContain(
- 'Fast-forward merge is not possible. To merge this request, first rebase locally.',
- );
- });
- });
-
- describe('when source branch protected', () => {
- beforeEach(() => {
- createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: true,
- conflictResolutionPath: TEST_HOST,
- sourceBranchProtected: true,
- conflictsDocsPath: '',
- },
+ describe('when source branch protected', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: TEST_HOST,
+ sourceBranchProtected: true,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('sets resolve button as disabled', () => {
+ expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe('true');
+ });
+
+ it('renders popover', () => {
+ expect($.fn.popover).toHaveBeenCalled();
+ });
});
- });
-
- it('sets resolve button as disabled', () => {
- expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe('disabled');
- });
- it('renders popover', () => {
- expect($.fn.popover).toHaveBeenCalled();
- });
- });
-
- describe('when source branch not protected', () => {
- beforeEach(() => {
- createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: true,
- conflictResolutionPath: TEST_HOST,
- sourceBranchProtected: false,
- conflictsDocsPath: '',
- },
+ describe('when source branch not protected', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: TEST_HOST,
+ sourceBranchProtected: false,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('sets resolve button as disabled', () => {
+ expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe(undefined);
+ });
+
+ it('renders popover', () => {
+ expect($.fn.popover).not.toHaveBeenCalled();
+ });
});
});
-
- it('sets resolve button as disabled', () => {
- expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe(undefined);
- });
-
- it('renders popover', () => {
- expect($.fn.popover).not.toHaveBeenCalled();
- });
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
index 3f03ebdb047..f45368bf443 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
@@ -1,40 +1,46 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue';
-
-describe('MRWidgetMissingBranch', () => {
- let vm;
-
- beforeEach(() => {
- const Component = Vue.extend(missingBranchComponent);
- vm = mountComponent(Component, { mr: { sourceBranchRemoved: true } });
- });
-
- afterEach(() => {
- vm.$destroy();
+import { shallowMount } from '@vue/test-utils';
+import MissingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue';
+
+let wrapper;
+
+function factory(sourceBranchRemoved, mergeRequestWidgetGraphql) {
+ wrapper = shallowMount(MissingBranchComponent, {
+ propsData: {
+ mr: { sourceBranchRemoved },
+ },
+ provide: {
+ glFeatures: { mergeRequestWidgetGraphql },
+ },
});
- describe('computed', () => {
- describe('missingBranchName', () => {
- it('should return proper branch name', () => {
- expect(vm.missingBranchName).toEqual('source');
+ if (mergeRequestWidgetGraphql) {
+ wrapper.setData({ state: { sourceBranchExists: !sourceBranchRemoved } });
+ }
- vm.mr.sourceBranchRemoved = false;
+ return wrapper.vm.$nextTick();
+}
- expect(vm.missingBranchName).toEqual('target');
- });
- });
+describe('MRWidgetMissingBranch', () => {
+ afterEach(() => {
+ wrapper.destroy();
});
- describe('template', () => {
- it('should have correct elements', () => {
- const el = vm.$el;
- const content = el.textContent.replace(/\n(\s)+/g, ' ').trim();
-
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy();
- expect(content.replace(/\s\s+/g, ' ')).toContain('source branch does not exist.');
- expect(content).toContain('Please restore it or use a different source branch');
+ [true, false].forEach(mergeRequestWidgetGraphql => {
+ describe(`widget GraphQL feature flag is ${
+ mergeRequestWidgetGraphql ? 'enabled' : 'disabled'
+ }`, () => {
+ it.each`
+ sourceBranchRemoved | branchName
+ ${true} | ${'source'}
+ ${false} | ${'target'}
+ `(
+ 'should set missing branch name as $branchName when sourceBranchRemoved is $sourceBranchRemoved',
+ async ({ sourceBranchRemoved, branchName }) => {
+ await factory(sourceBranchRemoved, mergeRequestWidgetGraphql);
+
+ expect(wrapper.find('[data-testid="missingBranchName"]').text()).toContain(branchName);
+ },
+ );
});
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
index 5326d63cb8a..f9490ac77ff 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -1,4 +1,5 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlFormCheckbox } from '@gitlab/ui';
import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
import { SQUASH_BEFORE_MERGE } from '~/vue_merge_request_widget/i18n';
@@ -20,17 +21,15 @@ describe('Squash before merge component', () => {
wrapper.destroy();
});
- const findLabel = () => wrapper.find('[data-testid="squashLabel"]');
+ const findCheckbox = () => wrapper.find(GlFormCheckbox);
describe('checkbox', () => {
- const findCheckbox = () => wrapper.find('.js-squash-checkbox');
-
it('is unchecked if passed value prop is false', () => {
createComponent({
value: false,
});
- expect(findCheckbox().element.checked).toBeFalsy();
+ expect(findCheckbox().vm.$attrs.checked).toBe(false);
});
it('is checked if passed value prop is true', () => {
@@ -38,22 +37,7 @@ describe('Squash before merge component', () => {
value: true,
});
- expect(findCheckbox().element.checked).toBeTruthy();
- });
-
- it('changes value on click', done => {
- createComponent({
- value: false,
- });
-
- findCheckbox().element.checked = true;
-
- findCheckbox().trigger('change');
-
- wrapper.vm.$nextTick(() => {
- expect(findCheckbox().element.checked).toBeTruthy();
- done();
- });
+ expect(findCheckbox().vm.$attrs.checked).toBe(true);
});
it('is disabled if isDisabled prop is true', () => {
@@ -62,31 +46,12 @@ describe('Squash before merge component', () => {
isDisabled: true,
});
- expect(findCheckbox().attributes('disabled')).toBeTruthy();
- });
- });
-
- describe('label', () => {
- describe.each`
- isDisabled | expectation
- ${true} | ${'grays out text if it is true'}
- ${false} | ${'does not gray out text if it is false'}
- `('isDisabled prop', ({ isDisabled, expectation }) => {
- beforeEach(() => {
- createComponent({
- value: false,
- isDisabled,
- });
- });
-
- it(expectation, () => {
- expect(findLabel().classes('gl-text-gray-400')).toBe(isDisabled);
- });
+ expect(findCheckbox().vm.$attrs.disabled).toBe(true);
});
});
describe('tooltip', () => {
- const tooltipTitle = () => findLabel().attributes('title');
+ const tooltipTitle = () => findCheckbox().attributes('title');
it('does not render when isDisabled is false', () => {
createComponent({
@@ -114,7 +79,7 @@ describe('Squash before merge component', () => {
const aboutLink = wrapper.find('a');
- expect(aboutLink.exists()).toBeFalsy();
+ expect(aboutLink.exists()).toBe(false);
});
it('is rendered if help path is passed', () => {
@@ -125,7 +90,7 @@ describe('Squash before merge component', () => {
const aboutLink = wrapper.find('a');
- expect(aboutLink.exists()).toBeTruthy();
+ expect(aboutLink.exists()).toBe(true);
});
it('should have a correct help path if passed', () => {
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
index 17d7fcc4bff..19a5566c3b1 100644
--- a/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
@@ -8,6 +8,7 @@ import {
SUCCESS,
FAILED,
CANCELED,
+ SKIPPED,
} from '~/vue_merge_request_widget/components/deployment/constants';
import { deploymentMockData, playDetails, retryDetails } from './deployment_mock_data';
@@ -77,6 +78,10 @@ describe('Deployment component', () => {
${CANCELED} | ${true} | ${noDetails} | ${'Canceled deployment to'} | ${defaultGroup}
${CANCELED} | ${false} | ${deployDetail} | ${'Canceled deployment to'} | ${noActions}
${CANCELED} | ${false} | ${noDetails} | ${'Canceled deployment to'} | ${noActions}
+ ${SKIPPED} | ${true} | ${deployDetail} | ${'Skipped deployment to'} | ${defaultGroup}
+ ${SKIPPED} | ${true} | ${noDetails} | ${'Skipped deployment to'} | ${defaultGroup}
+ ${SKIPPED} | ${false} | ${deployDetail} | ${'Skipped deployment to'} | ${noActions}
+ ${SKIPPED} | ${false} | ${noDetails} | ${'Skipped deployment to'} | ${noActions}
`(
'$status + previous: $previous + manual: $deploymentDetails.isManual',
({ status, previous, deploymentDetails, text, actionButtons }) => {
diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js
index 144283dc507..8ee920f06a1 100644
--- a/spec/frontend/vue_mr_widget/mock_data.js
+++ b/spec/frontend/vue_mr_widget/mock_data.js
@@ -41,6 +41,7 @@ export default {
user_callouts_path: 'some/callout/path',
suggest_pipeline_feature_id: 'suggest_pipeline',
new_project_pipeline_path: '/group2/project2/pipelines/new',
+ source_project_default_url: '/gitlab-org/html5-boilerplate.git',
metrics: {
merged_by: {
name: 'Administrator',
@@ -263,6 +264,8 @@ export default {
merge_trains_count: 3,
merge_train_index: 1,
security_reports_docs_path: 'security-reports-docs-path',
+ sast_comparison_path: '/sast_comparison_path',
+ secret_scanning_comparison_path: '/secret_scanning_comparison_path',
};
export const mockStore = {
diff --git a/spec/frontend/vue_mr_widget/mr_widget_how_to_merge_modal_spec.js b/spec/frontend/vue_mr_widget/mr_widget_how_to_merge_modal_spec.js
new file mode 100644
index 00000000000..aaaee3327a8
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/mr_widget_how_to_merge_modal_spec.js
@@ -0,0 +1,68 @@
+import { GlModal, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import MrWidgetHowToMergeModal from '~/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue';
+
+describe('MRWidgetHowToMerge', () => {
+ let wrapper;
+
+ function mountComponent({ data = {}, props = {} } = {}) {
+ wrapper = shallowMount(MrWidgetHowToMergeModal, {
+ data() {
+ return { ...data };
+ },
+ propsData: {
+ ...props,
+ },
+ stubs: {},
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ const findModal = () => wrapper.find(GlModal);
+ const findInstructionsFields = () =>
+ wrapper.findAll('[ data-testid="how-to-merge-instructions"]');
+ const findTipLink = () => wrapper.find(GlSprintf);
+
+ it('renders a modal', () => {
+ expect(findModal().exists()).toBe(true);
+ });
+
+ it('renders a selection of markdown fields', () => {
+ expect(findInstructionsFields().length).toBe(3);
+ });
+
+ it('renders a tip including a link to docs when a valid link is present', () => {
+ mountComponent({ props: { reviewingDocsPath: '/gitlab-org/help' } });
+ expect(findTipLink().exists()).toBe(true);
+ });
+
+ it('should not render a tip including a link to docs when a valid link is not present', () => {
+ expect(findTipLink().exists()).toBe(false);
+ });
+
+ it('should render different instructions based on if the user can merge', () => {
+ mountComponent({ props: { canMerge: true } });
+ expect(
+ findInstructionsFields()
+ .at(2)
+ .text(),
+ ).toContain('git push origin');
+ });
+
+ it('should render different instructions based on if the merge is based off a fork', () => {
+ mountComponent({ props: { isFork: true } });
+ expect(
+ findInstructionsFields()
+ .at(0)
+ .text(),
+ ).toContain('FETCH_HEAD');
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
index cb0006548d4..a20cd5b4400 100644
--- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -863,7 +863,7 @@ describe('mrWidgetOptions', () => {
});
});
- describe('suggestPipeline feature flag', () => {
+ describe('suggestPipeline', () => {
beforeEach(() => {
mock.onAny().reply(200);
@@ -874,8 +874,6 @@ describe('mrWidgetOptions', () => {
describe('given feature flag is enabled', () => {
beforeEach(() => {
- gon.features = { suggestPipeline: true };
-
createComponent();
vm.mr.hasCI = false;
@@ -905,19 +903,5 @@ describe('mrWidgetOptions', () => {
expect(findSuggestPipeline()).toBeNull();
});
});
-
- describe('given feature flag is not enabled', () => {
- beforeEach(() => {
- gon.features = { suggestPipeline: false };
-
- createComponent();
-
- vm.mr.hasCI = false;
- });
-
- it('should not suggest pipelines when none exist', () => {
- expect(findSuggestPipeline()).toBeNull();
- });
- });
});
});
diff --git a/spec/frontend/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/frontend/vue_mr_widget/stores/mr_widget_store_spec.js
index f73f78d6f6e..8b2c10ec50a 100644
--- a/spec/frontend/vue_mr_widget/stores/mr_widget_store_spec.js
+++ b/spec/frontend/vue_mr_widget/stores/mr_widget_store_spec.js
@@ -1,3 +1,4 @@
+import { convertToCamelCase } from '~/lib/utils/text_utility';
import MergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mockData from '../mock_data';
@@ -141,10 +142,29 @@ describe('MergeRequestStore', () => {
expect(store.newPipelinePath).toBe('/group2/project2/pipelines/new');
});
+ it('should set sourceProjectDefaultUrl', () => {
+ store.setPaths({ ...mockData });
+
+ expect(store.sourceProjectDefaultUrl).toBe('/gitlab-org/html5-boilerplate.git');
+ });
+
it('should set securityReportsDocsPath', () => {
store.setPaths({ ...mockData });
expect(store.securityReportsDocsPath).toBe('security-reports-docs-path');
});
+
+ it.each(['sast_comparison_path', 'secret_scanning_comparison_path'])(
+ 'should set %s path',
+ property => {
+ // Ensure something is set in the mock data
+ expect(property in mockData).toBe(true);
+ const expectedValue = mockData[property];
+
+ store.setPaths({ ...mockData });
+
+ expect(store[convertToCamelCase(property)]).toBe(expectedValue);
+ },
+ );
});
});
diff --git a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
index 04ae2a0f34d..20ea897e29c 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
@@ -5,12 +5,17 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
class="awards js-awards-block"
>
<button
- class="btn award-control"
+ class="btn gl-mr-3 btn-default btn-md gl-button"
data-testid="award-button"
title="Ada, Leonardo, and Marie"
type="button"
>
+ <!---->
+
+ <!---->
+
<span
+ class="award-emoji-block"
data-testid="award-html"
>
@@ -23,18 +28,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
- class="award-control-text js-counter"
+ class="gl-button-text"
>
- 3
+
+ <span
+ class="js-counter"
+ >
+ 3
+ </span>
</span>
</button>
<button
- class="btn award-control active"
+ class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You, Ada, and Marie"
type="button"
>
+ <!---->
+
+ <!---->
+
<span
+ class="award-emoji-block"
data-testid="award-html"
>
@@ -47,18 +62,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
- class="award-control-text js-counter"
+ class="gl-button-text"
>
- 3
+
+ <span
+ class="js-counter"
+ >
+ 3
+ </span>
</span>
</button>
<button
- class="btn award-control"
+ class="btn gl-mr-3 btn-default btn-md gl-button"
data-testid="award-button"
title="Ada and Jane"
type="button"
>
+ <!---->
+
+ <!---->
+
<span
+ class="award-emoji-block"
data-testid="award-html"
>
@@ -71,18 +96,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
- class="award-control-text js-counter"
+ class="gl-button-text"
>
- 2
+
+ <span
+ class="js-counter"
+ >
+ 2
+ </span>
</span>
</button>
<button
- class="btn award-control active"
+ class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You, Ada, Jane, and Leonardo"
type="button"
>
+ <!---->
+
+ <!---->
+
<span
+ class="award-emoji-block"
data-testid="award-html"
>
@@ -95,18 +130,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
- class="award-control-text js-counter"
+ class="gl-button-text"
>
- 4
+
+ <span
+ class="js-counter"
+ >
+ 4
+ </span>
</span>
</button>
<button
- class="btn award-control active"
+ class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You"
type="button"
>
+ <!---->
+
+ <!---->
+
<span
+ class="award-emoji-block"
data-testid="award-html"
>
@@ -119,18 +164,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
- class="award-control-text js-counter"
+ class="gl-button-text"
>
- 1
+
+ <span
+ class="js-counter"
+ >
+ 1
+ </span>
</span>
</button>
<button
- class="btn award-control"
+ class="btn gl-mr-3 btn-default btn-md gl-button"
data-testid="award-button"
title="Marie"
type="button"
>
+ <!---->
+
+ <!---->
+
<span
+ class="award-emoji-block"
data-testid="award-html"
>
@@ -143,18 +198,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
- class="award-control-text js-counter"
+ class="gl-button-text"
>
- 1
+
+ <span
+ class="js-counter"
+ >
+ 1
+ </span>
</span>
</button>
<button
- class="btn award-control active"
+ class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You"
type="button"
>
+ <!---->
+
+ <!---->
+
<span
+ class="award-emoji-block"
data-testid="award-html"
>
@@ -167,9 +232,14 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
- class="award-control-text js-counter"
+ class="gl-button-text"
>
- 1
+
+ <span
+ class="js-counter"
+ >
+ 1
+ </span>
</span>
</button>
@@ -178,46 +248,59 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
>
<button
aria-label="Add reaction"
- class="award-control btn js-add-award js-test-add-button-class"
+ class="btn add-reaction-button js-add-award btn-default btn-md gl-button js-test-add-button-class"
title="Add reaction"
type="button"
>
- <span
- class="award-control-icon award-control-icon-neutral"
- >
- <gl-icon-stub
- aria-hidden="true"
- name="slight-smile"
- size="16"
- />
- </span>
+ <!---->
+ <!---->
+
<span
- class="award-control-icon award-control-icon-positive"
+ class="gl-button-text"
>
- <gl-icon-stub
- aria-hidden="true"
- name="smiley"
- size="16"
- />
+ <span
+ class="reaction-control-icon reaction-control-icon-neutral"
+ >
+ <svg
+ aria-hidden="true"
+ class="gl-icon s16"
+ data-testid="slight-smile-icon"
+ >
+ <use
+ href="#slight-smile"
+ />
+ </svg>
+ </span>
+
+ <span
+ class="reaction-control-icon reaction-control-icon-positive"
+ >
+ <svg
+ aria-hidden="true"
+ class="gl-icon s16"
+ data-testid="smiley-icon"
+ >
+ <use
+ href="#smiley"
+ />
+ </svg>
+ </span>
+
+ <span
+ class="reaction-control-icon reaction-control-icon-super-positive"
+ >
+ <svg
+ aria-hidden="true"
+ class="gl-icon s16"
+ data-testid="smile-icon"
+ >
+ <use
+ href="#smile"
+ />
+ </svg>
+ </span>
</span>
-
- <span
- class="award-control-icon award-control-icon-super-positive"
- >
- <gl-icon-stub
- aria-hidden="true"
- name="smiley"
- size="16"
- />
- </span>
-
- <gl-loading-icon-stub
- class="award-control-icon-loading"
- color="dark"
- label="Loading"
- size="md"
- />
</button>
</div>
</div>
diff --git a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
index ec4a81054db..63d38e7587a 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
@@ -4,7 +4,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
<gl-dropdown-stub
category="primary"
headertext=""
- right=""
+ right="true"
size="medium"
text="Clone"
variant="info"
diff --git a/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap
index 19a649089e0..adb6c935f96 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap
@@ -11,6 +11,7 @@ exports[`Expand button on click when short text is provided renders button after
<!---->
<svg
+ aria-hidden="true"
class="gl-button-icon gl-icon s16"
data-testid="ellipsis_h-icon"
>
@@ -39,6 +40,7 @@ exports[`Expand button on click when short text is provided renders button after
<!---->
<svg
+ aria-hidden="true"
class="gl-button-icon gl-icon s16"
data-testid="ellipsis_h-icon"
>
@@ -62,6 +64,7 @@ exports[`Expand button when short text is provided renders button before text 1`
<!---->
<svg
+ aria-hidden="true"
class="gl-button-icon gl-icon s16"
data-testid="ellipsis_h-icon"
>
@@ -90,6 +93,7 @@ exports[`Expand button when short text is provided renders button before text 1`
<!---->
<svg
+ aria-hidden="true"
class="gl-button-icon gl-icon s16"
data-testid="ellipsis_h-icon"
>
diff --git a/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap
index 8eb0e8f9550..dd88ba9a6fb 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap
@@ -2,7 +2,7 @@
exports[`SplitButton renders actionItems 1`] = `
<gl-dropdown-stub
- category="tertiary"
+ category="primary"
headertext=""
menu-class=""
size="medium"
@@ -14,6 +14,7 @@ exports[`SplitButton renders actionItems 1`] = `
avatarurl=""
iconcolor=""
iconname=""
+ iconrightarialabel=""
iconrightname=""
ischecked="true"
ischeckitem="true"
@@ -33,6 +34,7 @@ exports[`SplitButton renders actionItems 1`] = `
avatarurl=""
iconcolor=""
iconname=""
+ iconrightarialabel=""
iconrightname=""
ischeckitem="true"
secondarytext=""
diff --git a/spec/frontend/vue_shared/components/awards_list_spec.js b/spec/frontend/vue_shared/components/awards_list_spec.js
index 63fc8a5749d..d20de81c446 100644
--- a/spec/frontend/vue_shared/components/awards_list_spec.js
+++ b/spec/frontend/vue_shared/components/awards_list_spec.js
@@ -1,4 +1,4 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import AwardsList from '~/vue_shared/components/awards_list.vue';
const createUser = (id, name) => ({ id, name });
@@ -41,6 +41,8 @@ const TEST_AWARDS = [
];
const TEST_ADD_BUTTON_CLASS = 'js-test-add-button-class';
+const REACTION_CONTROL_CLASSES = ['btn', 'gl-mr-3', 'btn-default', 'btn-md', 'gl-button'];
+
describe('vue_shared/components/awards_list', () => {
let wrapper;
@@ -54,16 +56,16 @@ describe('vue_shared/components/awards_list', () => {
throw new Error('There should only be one wrapper created per test');
}
- wrapper = shallowMount(AwardsList, { propsData: props });
+ wrapper = mount(AwardsList, { propsData: props });
};
const matchingEmojiTag = name => expect.stringMatching(`gl-emoji data-name="${name}"`);
- const findAwardButtons = () => wrapper.findAll('[data-testid="award-button"');
+ const findAwardButtons = () => wrapper.findAll('[data-testid="award-button"]');
const findAwardsData = () =>
findAwardButtons().wrappers.map(x => {
return {
classes: x.classes(),
title: x.attributes('title'),
- html: x.find('[data-testid="award-html"]').element.innerHTML,
+ html: x.find('[data-testid="award-html"]').html(),
count: Number(x.find('.js-counter').text()),
};
});
@@ -86,43 +88,43 @@ describe('vue_shared/components/awards_list', () => {
it('shows awards in correct order', () => {
expect(findAwardsData()).toEqual([
{
- classes: ['btn', 'award-control'],
+ classes: REACTION_CONTROL_CLASSES,
count: 3,
html: matchingEmojiTag(EMOJI_THUMBSUP),
title: 'Ada, Leonardo, and Marie',
},
{
- classes: ['btn', 'award-control', 'active'],
+ classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 3,
html: matchingEmojiTag(EMOJI_THUMBSDOWN),
title: 'You, Ada, and Marie',
},
{
- classes: ['btn', 'award-control'],
+ classes: REACTION_CONTROL_CLASSES,
count: 2,
html: matchingEmojiTag(EMOJI_SMILE),
title: 'Ada and Jane',
},
{
- classes: ['btn', 'award-control', 'active'],
+ classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 4,
html: matchingEmojiTag(EMOJI_OK),
title: 'You, Ada, Jane, and Leonardo',
},
{
- classes: ['btn', 'award-control', 'active'],
+ classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 1,
html: matchingEmojiTag(EMOJI_CACTUS),
title: 'You',
},
{
- classes: ['btn', 'award-control'],
+ classes: REACTION_CONTROL_CLASSES,
count: 1,
html: matchingEmojiTag(EMOJI_A),
title: 'Marie',
},
{
- classes: ['btn', 'award-control', 'active'],
+ classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 1,
html: matchingEmojiTag(EMOJI_B),
title: 'You',
@@ -135,7 +137,7 @@ describe('vue_shared/components/awards_list', () => {
findAwardButtons()
.at(2)
- .trigger('click');
+ .vm.$emit('click');
expect(wrapper.emitted().award).toEqual([[EMOJI_SMILE]]);
});
@@ -162,7 +164,7 @@ describe('vue_shared/components/awards_list', () => {
findAwardButtons()
.at(0)
- .trigger('click');
+ .vm.$emit('click');
expect(wrapper.emitted().award).toEqual([[Number(EMOJI_100)]]);
});
@@ -225,26 +227,26 @@ describe('vue_shared/components/awards_list', () => {
it('shows awards in correct order', () => {
expect(findAwardsData()).toEqual([
{
- classes: ['btn', 'award-control'],
+ classes: REACTION_CONTROL_CLASSES,
count: 0,
html: matchingEmojiTag(EMOJI_THUMBSUP),
title: '',
},
{
- classes: ['btn', 'award-control'],
+ classes: REACTION_CONTROL_CLASSES,
count: 0,
html: matchingEmojiTag(EMOJI_THUMBSDOWN),
title: '',
},
// We expect the EMOJI_100 before the EMOJI_SMILE because it was given as a defaultAward
{
- classes: ['btn', 'award-control'],
+ classes: REACTION_CONTROL_CLASSES,
count: 1,
html: matchingEmojiTag(EMOJI_100),
title: 'Marie',
},
{
- classes: ['btn', 'award-control'],
+ classes: REACTION_CONTROL_CLASSES,
count: 1,
html: matchingEmojiTag(EMOJI_SMILE),
title: 'Marie',
diff --git a/spec/frontend/vue_shared/components/callout_spec.js b/spec/frontend/vue_shared/components/callout_spec.js
deleted file mode 100644
index 7c9bb6b4650..00000000000
--- a/spec/frontend/vue_shared/components/callout_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import Callout from '~/vue_shared/components/callout.vue';
-
-const TEST_MESSAGE = 'This is a callout message!';
-const TEST_SLOT = '<button>This is a callout slot!</button>';
-
-describe('Callout Component', () => {
- let wrapper;
-
- const factory = options => {
- wrapper = shallowMount(Callout, {
- ...options,
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('should render the appropriate variant of callout', () => {
- factory({
- propsData: {
- category: 'info',
- message: TEST_MESSAGE,
- },
- });
-
- expect(wrapper.classes()).toEqual(['bs-callout', 'bs-callout-info']);
-
- expect(wrapper.element.tagName).toEqual('DIV');
- });
-
- it('should render accessibility attributes', () => {
- factory({
- propsData: {
- message: TEST_MESSAGE,
- },
- });
-
- expect(wrapper.attributes('role')).toEqual('alert');
- expect(wrapper.attributes('aria-live')).toEqual('assertive');
- });
-
- it('should render the provided message', () => {
- factory({
- propsData: {
- message: TEST_MESSAGE,
- },
- });
-
- expect(wrapper.element.innerHTML.trim()).toEqual(TEST_MESSAGE);
- });
-
- it('should render the provided slot', () => {
- factory({
- slots: {
- default: TEST_SLOT,
- },
- });
-
- expect(wrapper.element.innerHTML.trim()).toEqual(TEST_SLOT);
- });
-});
diff --git a/spec/frontend/vue_shared/components/clipboard_button_spec.js b/spec/frontend/vue_shared/components/clipboard_button_spec.js
index 51a2653befc..ac0be1537b7 100644
--- a/spec/frontend/vue_shared/components/clipboard_button_spec.js
+++ b/spec/frontend/vue_shared/components/clipboard_button_spec.js
@@ -1,16 +1,19 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
describe('clipboard button', () => {
let wrapper;
- const createWrapper = propsData => {
- wrapper = shallowMount(ClipboardButton, {
+ const createWrapper = (propsData, options = {}) => {
+ wrapper = mount(ClipboardButton, {
propsData,
+ ...options,
});
};
+ const findButton = () => wrapper.find(GlButton);
+
afterEach(() => {
wrapper.destroy();
wrapper = null;
@@ -26,7 +29,7 @@ describe('clipboard button', () => {
});
it('renders a button for clipboard', () => {
- expect(wrapper.find(GlButton).exists()).toBe(true);
+ expect(findButton().exists()).toBe(true);
expect(wrapper.attributes('data-clipboard-text')).toBe('copy me');
});
@@ -53,4 +56,35 @@ describe('clipboard button', () => {
);
});
});
+
+ it('renders default slot as button text', () => {
+ createWrapper(
+ {
+ text: 'copy me',
+ title: 'Copy this value',
+ },
+ {
+ slots: {
+ default: 'Foo bar',
+ },
+ },
+ );
+
+ expect(findButton().text()).toBe('Foo bar');
+ });
+
+ it('re-emits button events', () => {
+ const onClick = jest.fn();
+ createWrapper(
+ {
+ text: 'copy me',
+ title: 'Copy this value',
+ },
+ { listeners: { click: onClick } },
+ );
+
+ findButton().trigger('click');
+
+ expect(onClick).toHaveBeenCalled();
+ });
});
diff --git a/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js b/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js
new file mode 100644
index 00000000000..a50a4b742b3
--- /dev/null
+++ b/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js
@@ -0,0 +1,140 @@
+import { GlFormGroup, GlFormInput, GlFormInputGroup, GlLink } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
+
+import ColorPicker from '~/vue_shared/components/color_picker/color_picker.vue';
+
+describe('ColorPicker', () => {
+ let wrapper;
+
+ const createComponent = (fn = mount, propsData = {}) => {
+ wrapper = fn(ColorPicker, {
+ propsData,
+ });
+ };
+
+ const setColor = '#000000';
+ const label = () => wrapper.find(GlFormGroup).attributes('label');
+ const colorPreview = () => wrapper.find('[data-testid="color-preview"]');
+ const colorPicker = () => wrapper.find(GlFormInput);
+ const colorInput = () => wrapper.find(GlFormInputGroup).find('input[type="text"]');
+ const invalidFeedback = () => wrapper.find('.invalid-feedback');
+ const description = () => wrapper.find(GlFormGroup).attributes('description');
+ const presetColors = () => wrapper.findAll(GlLink);
+
+ beforeEach(() => {
+ gon.suggested_label_colors = {
+ [setColor]: 'Black',
+ '#0033CC': 'UA blue',
+ '#428BCA': 'Moderate blue',
+ '#44AD8E': 'Lime green',
+ };
+
+ createComponent(shallowMount);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('label', () => {
+ it('hides the label if the label is not passed', () => {
+ expect(label()).toBe('');
+ });
+
+ it('shows the label if the label is passed', () => {
+ createComponent(shallowMount, { label: 'test' });
+
+ expect(label()).toBe('test');
+ });
+ });
+
+ describe('behavior', () => {
+ it('by default has no values', () => {
+ createComponent();
+
+ expect(colorPreview().attributes('style')).toBe(undefined);
+ expect(colorPicker().attributes('value')).toBe(undefined);
+ expect(colorInput().props('value')).toBe('');
+ });
+
+ it('has a color set on initialization', () => {
+ createComponent(shallowMount, { setColor });
+
+ expect(wrapper.vm.$data.selectedColor).toBe(setColor);
+ });
+
+ it('emits input event from component when a color is selected', async () => {
+ createComponent();
+ await colorInput().setValue(setColor);
+
+ expect(wrapper.emitted().input[0]).toEqual([setColor]);
+ });
+
+ it('trims spaces from submitted colors', async () => {
+ createComponent();
+ await colorInput().setValue(` ${setColor} `);
+
+ expect(wrapper.vm.$data.selectedColor).toBe(setColor);
+ });
+
+ it('shows invalid feedback when an invalid color is used', async () => {
+ createComponent();
+ await colorInput().setValue('abcd');
+
+ expect(invalidFeedback().text()).toBe(
+ 'Please enter a valid hex (#RRGGBB or #RGB) color value',
+ );
+ expect(wrapper.emitted().input).toBe(undefined);
+ });
+
+ it('shows an invalid feedback border on the preview when an invalid color is used', async () => {
+ createComponent();
+ await colorInput().setValue('abcd');
+
+ expect(colorPreview().attributes('class')).toContain('gl-inset-border-1-red-500');
+ });
+ });
+
+ describe('inputs', () => {
+ it('has color input value entered', async () => {
+ createComponent();
+ await colorInput().setValue(setColor);
+
+ expect(wrapper.vm.$data.selectedColor).toBe(setColor);
+ });
+
+ it('has color picker value entered', async () => {
+ createComponent();
+ await colorPicker().setValue(setColor);
+
+ expect(wrapper.vm.$data.selectedColor).toBe(setColor);
+ });
+ });
+
+ describe('preset colors', () => {
+ it('hides the suggested colors if they are empty', () => {
+ gon.suggested_label_colors = {};
+ createComponent(shallowMount);
+
+ expect(description()).toBe('Choose any color');
+ expect(presetColors().exists()).toBe(false);
+ });
+
+ it('shows the suggested colors', () => {
+ createComponent(shallowMount);
+ expect(description()).toBe(
+ 'Choose any color. Or you can choose one of the suggested colors below',
+ );
+ expect(presetColors()).toHaveLength(4);
+ });
+
+ it('has preset color selected', async () => {
+ createComponent();
+ await presetColors()
+ .at(0)
+ .trigger('click');
+
+ expect(wrapper.vm.$data.selectedColor).toBe(setColor);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
index 64bfff3dfa1..8cc5d6775a7 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
@@ -17,11 +17,14 @@ import RecentSearchesService from '~/filtered_search/services/recent_searches_se
import {
mockAvailableTokens,
+ mockMembershipToken,
+ mockMembershipTokenOptionsWithoutTitles,
mockSortOptions,
mockHistoryItems,
tokenValueAuthor,
tokenValueLabel,
tokenValueMilestone,
+ tokenValueMembership,
} from './mock_data';
jest.mock('~/vue_shared/components/filtered_search_bar/filtered_search_utils', () => ({
@@ -412,6 +415,42 @@ describe('FilteredSearchBarRoot', () => {
wrapperFullMount.destroy();
});
+ describe('when token options have `title` attribute defined', () => {
+ it('renders search history items using the provided `title` attribute', async () => {
+ const wrapperFullMount = createComponent({
+ sortOptions: mockSortOptions,
+ tokens: [mockMembershipToken],
+ shallow: false,
+ });
+
+ wrapperFullMount.vm.recentSearchesStore.addRecentSearch([tokenValueMembership]);
+
+ await wrapperFullMount.vm.$nextTick();
+
+ expect(wrapperFullMount.find(GlDropdownItem).text()).toBe('Membership := Direct');
+
+ wrapperFullMount.destroy();
+ });
+ });
+
+ describe('when token options have do not have `title` attribute defined', () => {
+ it('renders search history items using the provided `value` attribute', async () => {
+ const wrapperFullMount = createComponent({
+ sortOptions: mockSortOptions,
+ tokens: [mockMembershipTokenOptionsWithoutTitles],
+ shallow: false,
+ });
+
+ wrapperFullMount.vm.recentSearchesStore.addRecentSearch([tokenValueMembership]);
+
+ await wrapperFullMount.vm.$nextTick();
+
+ expect(wrapperFullMount.find(GlDropdownItem).text()).toBe('Membership := exclude');
+
+ wrapperFullMount.destroy();
+ });
+ });
+
it('renders sort dropdown component', () => {
expect(wrapper.find(GlButtonGroup).exists()).toBe(true);
expect(wrapper.find(GlDropdown).exists()).toBe(true);
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
index e0a3208cac9..64fbe70696d 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
@@ -1,3 +1,4 @@
+import { GlFilteredSearchToken } from '@gitlab/ui';
import { mockLabels } from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
import Api from '~/api';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
@@ -102,6 +103,21 @@ export const mockMilestoneToken = {
fetchMilestones: () => Promise.resolve({ data: mockMilestones }),
};
+export const mockMembershipToken = {
+ type: 'with_inherited_permissions',
+ icon: 'group',
+ title: 'Membership',
+ token: GlFilteredSearchToken,
+ unique: true,
+ operators: [{ value: '=', description: 'is' }],
+ options: [{ value: 'exclude', title: 'Direct' }, { value: 'only', title: 'Inherited' }],
+};
+
+export const mockMembershipTokenOptionsWithoutTitles = {
+ ...mockMembershipToken,
+ options: [{ value: 'exclude' }, { value: 'only' }],
+};
+
export const mockAvailableTokens = [mockAuthorToken, mockLabelToken, mockMilestoneToken];
export const tokenValueAuthor = {
@@ -128,6 +144,14 @@ export const tokenValueMilestone = {
},
};
+export const tokenValueMembership = {
+ type: 'with_inherited_permissions',
+ value: {
+ operator: '=',
+ data: 'exclude',
+ },
+};
+
export const tokenValuePlain = {
type: 'filtered-search-term',
value: { data: 'foo' },
diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap b/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap
new file mode 100644
index 00000000000..d0fa2086fdc
--- /dev/null
+++ b/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap
@@ -0,0 +1,47 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`gfm_autocomplete/utils issues config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context issue title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
+
+exports[`gfm_autocomplete/utils issues config shows the reference and title in the menu item within a group context 1`] = `"<small>gitlab#987654</small> Group context issue title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
+
+exports[`gfm_autocomplete/utils labels config shows the title in the menu item 1`] = `
+"
+ <span class=\\"dropdown-label-box\\" style=\\"background: #123456;\\"></span>
+ bug &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"
+`;
+
+exports[`gfm_autocomplete/utils members config shows an avatar character, name, parent name, and count in the menu item for a group 1`] = `
+"
+ <div class=\\"gl-display-flex gl-align-items-center\\">
+ <div class=\\"gl-avatar gl-avatar-s24 gl-flex-shrink-0 gl-rounded-small
+ gl-display-flex gl-align-items-center gl-justify-content-center\\" aria-hidden=\\"true\\">
+ G</div>
+ <div class=\\"gl-font-sm gl-line-height-normal gl-ml-3\\">
+ <div>1-1s &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt; (2)</div>
+ <div class=\\"gl-text-gray-700\\">GitLab Support Team</div>
+ </div>
+
+ </div>
+ "
+`;
+
+exports[`gfm_autocomplete/utils members config shows the avatar, name and username in the menu item for a user 1`] = `
+"
+ <div class=\\"gl-display-flex gl-align-items-center\\">
+ <img class=\\"gl-avatar gl-avatar-s24 gl-flex-shrink-0 gl-avatar-circle\\" src=\\"/uploads/-/system/user/avatar/123456/avatar.png\\" alt=\\"\\" />
+ <div class=\\"gl-font-sm gl-line-height-normal gl-ml-3\\">
+ <div>My Name &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;</div>
+ <div class=\\"gl-text-gray-700\\">@myusername</div>
+ </div>
+
+ </div>
+ "
+`;
+
+exports[`gfm_autocomplete/utils merge requests config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context merge request title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
+
+exports[`gfm_autocomplete/utils merge requests config shows the reference and title in the menu item within a group context 1`] = `"<small>gitlab!456789</small> Group context merge request title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
+
+exports[`gfm_autocomplete/utils milestones config shows the title in the menu item 1`] = `"13.2 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
+
+exports[`gfm_autocomplete/utils snippets config shows the id and title in the menu item 1`] = `"<small>123456</small> Snippet title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
diff --git a/spec/frontend/vue_shared/components/gl_mentions_spec.js b/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js
index 32fc055a77d..b4002fdf4ec 100644
--- a/spec/frontend/vue_shared/components/gl_mentions_spec.js
+++ b/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js
@@ -1,15 +1,15 @@
+import Tribute from '@gitlab/tributejs';
import { shallowMount } from '@vue/test-utils';
-import Tribute from 'tributejs';
-import GlMentions from '~/vue_shared/components/gl_mentions.vue';
+import GfmAutocomplete from '~/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue';
-describe('GlMentions', () => {
+describe('GfmAutocomplete', () => {
let wrapper;
- describe('Tribute', () => {
+ describe('tribute', () => {
const mentions = '/gitlab-org/gitlab-test/-/autocomplete_sources/members?type=Issue&type_id=1';
beforeEach(() => {
- wrapper = shallowMount(GlMentions, {
+ wrapper = shallowMount(GfmAutocomplete, {
propsData: {
dataSources: {
mentions,
diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js b/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js
new file mode 100644
index 00000000000..647f8c6e000
--- /dev/null
+++ b/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js
@@ -0,0 +1,344 @@
+import { escape, last } from 'lodash';
+import { GfmAutocompleteType, tributeConfig } from '~/vue_shared/components/gfm_autocomplete/utils';
+
+describe('gfm_autocomplete/utils', () => {
+ describe('issues config', () => {
+ const issuesConfig = tributeConfig[GfmAutocompleteType.Issues].config;
+ const groupContextIssue = {
+ iid: 987654,
+ reference: 'gitlab#987654',
+ title: "Group context issue title <script>alert('hi')</script>",
+ };
+ const projectContextIssue = {
+ id: null,
+ iid: 123456,
+ time_estimate: 0,
+ title: "Project context issue title <script>alert('hi')</script>",
+ };
+
+ it('uses # as the trigger', () => {
+ expect(issuesConfig.trigger).toBe('#');
+ });
+
+ it('searches using both the iid and title', () => {
+ expect(issuesConfig.lookup(projectContextIssue)).toBe(
+ `${projectContextIssue.iid}${projectContextIssue.title}`,
+ );
+ });
+
+ it('shows the reference and title in the menu item within a group context', () => {
+ expect(issuesConfig.menuItemTemplate({ original: groupContextIssue })).toMatchSnapshot();
+ });
+
+ it('shows the iid and title in the menu item within a project context', () => {
+ expect(issuesConfig.menuItemTemplate({ original: projectContextIssue })).toMatchSnapshot();
+ });
+
+ it('inserts the reference on autocomplete selection within a group context', () => {
+ expect(issuesConfig.selectTemplate({ original: groupContextIssue })).toBe(
+ groupContextIssue.reference,
+ );
+ });
+
+ it('inserts the iid on autocomplete selection within a project context', () => {
+ expect(issuesConfig.selectTemplate({ original: projectContextIssue })).toBe(
+ `#${projectContextIssue.iid}`,
+ );
+ });
+ });
+
+ describe('labels config', () => {
+ const labelsConfig = tributeConfig[GfmAutocompleteType.Labels].config;
+ const labelsFilter = tributeConfig[GfmAutocompleteType.Labels].filterValues;
+ const label = {
+ color: '#123456',
+ textColor: '#FFFFFF',
+ title: `bug <script>alert('hi')</script>`,
+ type: 'GroupLabel',
+ };
+ const singleWordLabel = {
+ color: '#456789',
+ textColor: '#DDD',
+ title: `bug`,
+ type: 'GroupLabel',
+ };
+ const numericalLabel = {
+ color: '#abcdef',
+ textColor: '#AAA',
+ title: 123456,
+ type: 'ProjectLabel',
+ };
+
+ it('uses ~ as the trigger', () => {
+ expect(labelsConfig.trigger).toBe('~');
+ });
+
+ it('searches using `title`', () => {
+ expect(labelsConfig.lookup).toBe('title');
+ });
+
+ it('shows the title in the menu item', () => {
+ expect(labelsConfig.menuItemTemplate({ original: label })).toMatchSnapshot();
+ });
+
+ it('inserts the title on autocomplete selection', () => {
+ expect(labelsConfig.selectTemplate({ original: singleWordLabel })).toBe(
+ `~${escape(singleWordLabel.title)}`,
+ );
+ });
+
+ it('inserts the title enclosed with quotes on autocomplete selection when the title is numerical', () => {
+ expect(labelsConfig.selectTemplate({ original: numericalLabel })).toBe(
+ `~"${escape(numericalLabel.title)}"`,
+ );
+ });
+
+ it('inserts the title enclosed with quotes on autocomplete selection when the title contains multiple words', () => {
+ expect(labelsConfig.selectTemplate({ original: label })).toBe(`~"${escape(label.title)}"`);
+ });
+
+ describe('filter', () => {
+ const collection = [label, singleWordLabel, { ...numericalLabel, set: true }];
+
+ describe('/label quick action', () => {
+ describe('when the line starts with `/label`', () => {
+ it('shows labels that are not currently selected', () => {
+ const fullText = '/label ~';
+ const selectionStart = 8;
+
+ expect(labelsFilter({ collection, fullText, selectionStart })).toEqual([
+ collection[0],
+ collection[1],
+ ]);
+ });
+ });
+
+ describe('when the line does not start with `/label`', () => {
+ it('shows all labels', () => {
+ const fullText = '~';
+ const selectionStart = 1;
+
+ expect(labelsFilter({ collection, fullText, selectionStart })).toEqual(collection);
+ });
+ });
+ });
+
+ describe('/unlabel quick action', () => {
+ describe('when the line starts with `/unlabel`', () => {
+ it('shows labels that are currently selected', () => {
+ const fullText = '/unlabel ~';
+ const selectionStart = 10;
+
+ expect(labelsFilter({ collection, fullText, selectionStart })).toEqual([collection[2]]);
+ });
+ });
+
+ describe('when the line does not start with `/unlabel`', () => {
+ it('shows all labels', () => {
+ const fullText = '~';
+ const selectionStart = 1;
+
+ expect(labelsFilter({ collection, fullText, selectionStart })).toEqual(collection);
+ });
+ });
+ });
+ });
+ });
+
+ describe('members config', () => {
+ const membersConfig = tributeConfig[GfmAutocompleteType.Members].config;
+ const membersFilter = tributeConfig[GfmAutocompleteType.Members].filterValues;
+ const userMember = {
+ type: 'User',
+ username: 'myusername',
+ name: "My Name <script>alert('hi')</script>",
+ avatar_url: '/uploads/-/system/user/avatar/123456/avatar.png',
+ availability: null,
+ };
+ const groupMember = {
+ type: 'Group',
+ username: 'gitlab-com/support/1-1s',
+ name: "GitLab.com / GitLab Support Team / 1-1s <script>alert('hi')</script>",
+ avatar_url: null,
+ count: 2,
+ mentionsDisabled: null,
+ };
+
+ it('uses @ as the trigger', () => {
+ expect(membersConfig.trigger).toBe('@');
+ });
+
+ it('inserts the username on autocomplete selection', () => {
+ expect(membersConfig.fillAttr).toBe('username');
+ });
+
+ it('searches using both the name and username for a user', () => {
+ expect(membersConfig.lookup(userMember)).toBe(`${userMember.name}${userMember.username}`);
+ });
+
+ it('searches using only its own name and not its ancestors for a group', () => {
+ expect(membersConfig.lookup(groupMember)).toBe(last(groupMember.name.split(' / ')));
+ });
+
+ it('shows the avatar, name and username in the menu item for a user', () => {
+ expect(membersConfig.menuItemTemplate({ original: userMember })).toMatchSnapshot();
+ });
+
+ it('shows an avatar character, name, parent name, and count in the menu item for a group', () => {
+ expect(membersConfig.menuItemTemplate({ original: groupMember })).toMatchSnapshot();
+ });
+
+ describe('filter', () => {
+ const assignees = [userMember.username];
+ const collection = [userMember, groupMember];
+
+ describe('/assign quick action', () => {
+ describe('when the line starts with `/assign`', () => {
+ it('shows members that are not currently selected', () => {
+ const fullText = '/assign @';
+ const selectionStart = 9;
+
+ expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual([
+ collection[1],
+ ]);
+ });
+ });
+
+ describe('when the line does not start with `/assign`', () => {
+ it('shows all labels', () => {
+ const fullText = '@';
+ const selectionStart = 1;
+
+ expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual(
+ collection,
+ );
+ });
+ });
+ });
+
+ describe('/unassign quick action', () => {
+ describe('when the line starts with `/unassign`', () => {
+ it('shows members that are currently selected', () => {
+ const fullText = '/unassign @';
+ const selectionStart = 11;
+
+ expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual([
+ collection[0],
+ ]);
+ });
+ });
+
+ describe('when the line does not start with `/unassign`', () => {
+ it('shows all members', () => {
+ const fullText = '@';
+ const selectionStart = 1;
+
+ expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual(
+ collection,
+ );
+ });
+ });
+ });
+ });
+ });
+
+ describe('merge requests config', () => {
+ const mergeRequestsConfig = tributeConfig[GfmAutocompleteType.MergeRequests].config;
+ const groupContextMergeRequest = {
+ iid: 456789,
+ reference: 'gitlab!456789',
+ title: "Group context merge request title <script>alert('hi')</script>",
+ };
+ const projectContextMergeRequest = {
+ id: null,
+ iid: 123456,
+ time_estimate: 0,
+ title: "Project context merge request title <script>alert('hi')</script>",
+ };
+
+ it('uses ! as the trigger', () => {
+ expect(mergeRequestsConfig.trigger).toBe('!');
+ });
+
+ it('searches using both the iid and title', () => {
+ expect(mergeRequestsConfig.lookup(projectContextMergeRequest)).toBe(
+ `${projectContextMergeRequest.iid}${projectContextMergeRequest.title}`,
+ );
+ });
+
+ it('shows the reference and title in the menu item within a group context', () => {
+ expect(
+ mergeRequestsConfig.menuItemTemplate({ original: groupContextMergeRequest }),
+ ).toMatchSnapshot();
+ });
+
+ it('shows the iid and title in the menu item within a project context', () => {
+ expect(
+ mergeRequestsConfig.menuItemTemplate({ original: projectContextMergeRequest }),
+ ).toMatchSnapshot();
+ });
+
+ it('inserts the reference on autocomplete selection within a group context', () => {
+ expect(mergeRequestsConfig.selectTemplate({ original: groupContextMergeRequest })).toBe(
+ groupContextMergeRequest.reference,
+ );
+ });
+
+ it('inserts the iid on autocomplete selection within a project context', () => {
+ expect(mergeRequestsConfig.selectTemplate({ original: projectContextMergeRequest })).toBe(
+ `!${projectContextMergeRequest.iid}`,
+ );
+ });
+ });
+
+ describe('milestones config', () => {
+ const milestonesConfig = tributeConfig[GfmAutocompleteType.Milestones].config;
+ const milestone = {
+ id: null,
+ iid: 49,
+ title: "13.2 <script>alert('hi')</script>",
+ };
+
+ it('uses % as the trigger', () => {
+ expect(milestonesConfig.trigger).toBe('%');
+ });
+
+ it('searches using the title', () => {
+ expect(milestonesConfig.lookup).toBe('title');
+ });
+
+ it('shows the title in the menu item', () => {
+ expect(milestonesConfig.menuItemTemplate({ original: milestone })).toMatchSnapshot();
+ });
+
+ it('inserts the title on autocomplete selection', () => {
+ expect(milestonesConfig.selectTemplate({ original: milestone })).toBe(
+ `%"${escape(milestone.title)}"`,
+ );
+ });
+ });
+
+ describe('snippets config', () => {
+ const snippetsConfig = tributeConfig[GfmAutocompleteType.Snippets].config;
+ const snippet = {
+ id: 123456,
+ title: "Snippet title <script>alert('hi')</script>",
+ };
+
+ it('uses $ as the trigger', () => {
+ expect(snippetsConfig.trigger).toBe('$');
+ });
+
+ it('inserts the id on autocomplete selection', () => {
+ expect(snippetsConfig.fillAttr).toBe('id');
+ });
+
+ it('searches using both the id and title', () => {
+ expect(snippetsConfig.lookup(snippet)).toBe(`${snippet.id}${snippet.title}`);
+ });
+
+ it('shows the id and title in the menu item', () => {
+ expect(snippetsConfig.menuItemTemplate({ original: snippet })).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
index c87d19df1f7..d1bfc180082 100644
--- a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
+++ b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
@@ -33,28 +33,31 @@ describe('IssueMilestoneComponent', () => {
describe('computed', () => {
describe('isMilestoneStarted', () => {
- it('should return `false` when milestoneStart prop is not defined', () => {
+ it('should return `false` when milestoneStart prop is not defined', async () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '' },
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.isMilestoneStarted).toBe(false);
});
- it('should return `true` when milestone start date is past current date', () => {
- wrapper.setProps({
+ it('should return `true` when milestone start date is past current date', async () => {
+ await wrapper.setProps({
milestone: { ...mockMilestone, start_date: '1990-07-22' },
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.isMilestoneStarted).toBe(true);
});
});
describe('isMilestonePastDue', () => {
- it('should return `false` when milestoneDue prop is not defined', () => {
+ it('should return `false` when milestoneDue prop is not defined', async () => {
wrapper.setProps({
milestone: { ...mockMilestone, due_date: '' },
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.isMilestonePastDue).toBe(false);
});
@@ -73,41 +76,45 @@ describe('IssueMilestoneComponent', () => {
expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)');
});
- it('returns string containing absolute milestone start date when due date is not present', () => {
+ it('returns string containing absolute milestone start date when due date is not present', async () => {
wrapper.setProps({
milestone: { ...mockMilestone, due_date: '' },
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.milestoneDatesAbsolute).toBe('(January 1, 2018)');
});
- it('returns empty string when both milestone start and due dates are not present', () => {
+ it('returns empty string when both milestone start and due dates are not present', async () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '', due_date: '' },
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.milestoneDatesAbsolute).toBe('');
});
});
describe('milestoneDatesHuman', () => {
- it('returns string containing milestone due date when date is yet to be due', () => {
+ it('returns string containing milestone due date when date is yet to be due', async () => {
wrapper.setProps({
milestone: { ...mockMilestone, due_date: `${new Date().getFullYear() + 10}-01-01` },
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.milestoneDatesHuman).toContain('years remaining');
});
- it('returns string containing milestone start date when date has already started and due date is not present', () => {
+ it('returns string containing milestone start date when date has already started and due date is not present', async () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '1990-07-22', due_date: '' },
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.milestoneDatesHuman).toContain('Started');
});
- it('returns string containing milestone start date when date is yet to start and due date is not present', () => {
+ it('returns string containing milestone start date when date is yet to start and due date is not present', async () => {
wrapper.setProps({
milestone: {
...mockMilestone,
@@ -115,14 +122,16 @@ describe('IssueMilestoneComponent', () => {
due_date: '',
},
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.milestoneDatesHuman).toContain('Starts');
});
- it('returns empty string when milestone start and due dates are not present', () => {
+ it('returns empty string when milestone start and due dates are not present', async () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '', due_date: '' },
});
+ await wrapper.vm.$nextTick();
expect(wrapper.vm.milestoneDatesHuman).toBe('');
});
diff --git a/spec/frontend/vue_shared/components/loading_button_spec.js b/spec/frontend/vue_shared/components/loading_button_spec.js
deleted file mode 100644
index 8bcb80d140e..00000000000
--- a/spec/frontend/vue_shared/components/loading_button_spec.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import LoadingButton from '~/vue_shared/components/loading_button.vue';
-
-const LABEL = 'Hello';
-
-describe('LoadingButton', () => {
- let wrapper;
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const buildWrapper = (propsData = {}) => {
- wrapper = shallowMount(LoadingButton, {
- propsData,
- });
- };
- const findButtonLabel = () => wrapper.find('.js-loading-button-label');
- const findButtonIcon = () => wrapper.find('.js-loading-button-icon');
-
- describe('loading spinner', () => {
- it('shown when loading', () => {
- buildWrapper({ loading: true });
-
- expect(findButtonIcon().exists()).toBe(true);
- });
- });
-
- describe('disabled state', () => {
- it('disabled when loading', () => {
- buildWrapper({ loading: true });
- expect(wrapper.attributes('disabled')).toBe('disabled');
- });
-
- it('not disabled when normal', () => {
- buildWrapper({ loading: false });
-
- expect(wrapper.attributes('disabled')).toBe(undefined);
- });
- });
-
- describe('label', () => {
- it('shown when normal', () => {
- buildWrapper({ loading: false, label: LABEL });
- expect(findButtonLabel().text()).toBe(LABEL);
- });
-
- it('shown when loading', () => {
- buildWrapper({ loading: false, label: LABEL });
- expect(findButtonLabel().text()).toBe(LABEL);
- });
- });
-
- describe('container class', () => {
- it('should default to btn btn-align-content', () => {
- buildWrapper();
-
- expect(wrapper.classes()).toContain('btn');
- expect(wrapper.classes()).toContain('btn-align-content');
- });
-
- it('should be configurable through props', () => {
- const containerClass = 'test-class';
-
- buildWrapper({
- containerClass,
- });
-
- expect(wrapper.classes()).not.toContain('btn');
- expect(wrapper.classes()).not.toContain('btn-align-content');
- expect(wrapper.classes()).toContain(containerClass);
- });
- });
-
- describe('click callback prop', () => {
- it('calls given callback when normal', () => {
- buildWrapper({
- loading: false,
- });
-
- wrapper.trigger('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('click')).toBeTruthy();
- });
- });
-
- it('does not call given callback when disabled because of loading', () => {
- buildWrapper({
- loading: true,
- });
-
- wrapper.trigger('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('click')).toBeFalsy();
- });
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js
new file mode 100644
index 00000000000..0598506891b
--- /dev/null
+++ b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js
@@ -0,0 +1,72 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlDropdown, GlFormTextarea, GlButton } from '@gitlab/ui';
+import ApplySuggestionComponent from '~/vue_shared/components/markdown/apply_suggestion.vue';
+
+describe('Apply Suggestion component', () => {
+ const propsData = { fileName: 'test.js', disabled: false };
+ let wrapper;
+
+ const createWrapper = props => {
+ wrapper = shallowMount(ApplySuggestionComponent, { propsData: { ...propsData, ...props } });
+ };
+
+ const findDropdown = () => wrapper.find(GlDropdown);
+ const findTextArea = () => wrapper.find(GlFormTextarea);
+ const findApplyButton = () => wrapper.find(GlButton);
+
+ beforeEach(() => createWrapper());
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('initial template', () => {
+ it('renders a dropdown with the correct props', () => {
+ const dropdown = findDropdown();
+
+ expect(dropdown.exists()).toBe(true);
+ expect(dropdown.props('text')).toBe('Apply suggestion');
+ expect(dropdown.props('headerText')).toBe('Apply suggestion commit message');
+ expect(dropdown.props('disabled')).toBe(false);
+ });
+
+ it('renders a textarea with the correct props', () => {
+ const textArea = findTextArea();
+
+ expect(textArea.exists()).toBe(true);
+ expect(textArea.attributes('placeholder')).toBe('Apply suggestion on test.js');
+ });
+
+ it('renders an apply button', () => {
+ const applyButton = findApplyButton();
+
+ expect(applyButton.exists()).toBe(true);
+ expect(applyButton.text()).toBe('Apply');
+ });
+ });
+
+ describe('disabled', () => {
+ it('disables the dropdown', () => {
+ createWrapper({ disabled: true });
+
+ expect(findDropdown().props('disabled')).toBe(true);
+ });
+ });
+
+ describe('apply suggestion', () => {
+ it('emits an apply event with a default message if no message was added', () => {
+ findTextArea().vm.$emit('input', null);
+ findApplyButton().vm.$emit('click');
+
+ expect(wrapper.emitted('apply')).toEqual([['Apply suggestion on test.js']]);
+ });
+
+ it('emits an apply event with a user-defined message', () => {
+ findTextArea().vm.$emit('input', 'some text');
+ findApplyButton().vm.$emit('click');
+
+ expect(wrapper.emitted('apply')).toEqual([['some text']]);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/registry/__snapshots__/code_instruction_spec.js.snap b/spec/frontend/vue_shared/components/registry/__snapshots__/code_instruction_spec.js.snap
index ecea151fc8a..da49778f216 100644
--- a/spec/frontend/vue_shared/components/registry/__snapshots__/code_instruction_spec.js.snap
+++ b/spec/frontend/vue_shared/components/registry/__snapshots__/code_instruction_spec.js.snap
@@ -47,6 +47,7 @@ exports[`Package code instruction single line to match the default snapshot 1`]
<!---->
<svg
+ aria-hidden="true"
class="gl-button-icon gl-icon s16"
data-testid="copy-to-clipboard-icon"
>
diff --git a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap
index 3990248d021..623f7d083c5 100644
--- a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap
+++ b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap
@@ -10,6 +10,9 @@ exports[`Resizable Skeleton Loader default setup renders the bars, labels, and g
version="1.1"
viewBox="0 0 400 130"
>
+ <title>
+ Loading
+ </title>
<rect
clip-path="url(#null-idClip)"
height="130"
@@ -226,6 +229,9 @@ exports[`Resizable Skeleton Loader with custom settings renders the correct posi
version="1.1"
viewBox="0 0 400 130"
>
+ <title>
+ Loading
+ </title>
<rect
clip-path="url(#-idClip)"
height="130"
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
index d50cf2915e8..cd1157a1c2e 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { mockEditorApi } from '@toast-ui/vue-editor';
import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue';
import InsertVideoModal from '~/vue_shared/components/rich_content_editor/modals/insert_video_modal.vue';
@@ -114,10 +115,17 @@ describe('Rich Content Editor', () => {
});
describe('when editor is loaded', () => {
+ const formattedMarkdown = 'formatted markdown';
+
beforeEach(() => {
+ mockEditorApi.getMarkdown.mockReturnValueOnce(formattedMarkdown);
buildWrapper();
});
+ afterEach(() => {
+ mockEditorApi.getMarkdown.mockReset();
+ });
+
it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
expect(addCustomEventListener).toHaveBeenCalledWith(
wrapper.vm.editorApi,
@@ -137,6 +145,11 @@ describe('Rich Content Editor', () => {
it('registers HTML to markdown renderer', () => {
expect(registerHTMLToMarkdownRenderer).toHaveBeenCalledWith(wrapper.vm.editorApi);
});
+
+ it('emits load event with the markdown formatted by Toast UI', () => {
+ expect(mockEditorApi.getMarkdown).toHaveBeenCalled();
+ expect(wrapper.emitted('load')[0]).toEqual([{ formattedMarkdown }]);
+ });
});
describe('when editor is destroyed', () => {
diff --git a/spec/frontend/vue_shared/components/security_reports/__snapshots__/security_summary_spec.js.snap b/spec/frontend/vue_shared/components/security_reports/__snapshots__/security_summary_spec.js.snap
new file mode 100644
index 00000000000..1e08394dd56
--- /dev/null
+++ b/spec/frontend/vue_shared/components/security_reports/__snapshots__/security_summary_spec.js.snap
@@ -0,0 +1,144 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SecuritySummary component given the message {"countMessage": "%{criticalStart}0 Critical%{criticalEnd} %{highStart}1 High%{highEnd} and %{otherStart}0 Others%{otherEnd}", "critical": 0, "high": 1, "message": "Security scanning detected %{totalStart}1%{totalEnd} potential vulnerability", "other": 0, "status": "", "total": 1} interpolates correctly 1`] = `
+<span>
+ Security scanning detected
+ <strong>
+ 1
+ </strong>
+ potential vulnerability
+ <span
+ class="gl-font-sm"
+ >
+ <span>
+ <span
+ class="gl-pl-4"
+ >
+
+ 0 Critical
+
+ </span>
+ </span>
+
+ <span>
+ <strong
+ class="text-danger-600 gl-px-2"
+ >
+
+ 1 High
+
+ </strong>
+ </span>
+ and
+ <span>
+ <span
+ class="gl-px-2"
+ >
+
+ 0 Others
+
+ </span>
+ </span>
+ </span>
+</span>
+`;
+
+exports[`SecuritySummary component given the message {"countMessage": "%{criticalStart}1 Critical%{criticalEnd} %{highStart}0 High%{highEnd} and %{otherStart}0 Others%{otherEnd}", "critical": 1, "high": 0, "message": "Security scanning detected %{totalStart}1%{totalEnd} potential vulnerability", "other": 0, "status": "", "total": 1} interpolates correctly 1`] = `
+<span>
+ Security scanning detected
+ <strong>
+ 1
+ </strong>
+ potential vulnerability
+ <span
+ class="gl-font-sm"
+ >
+ <span>
+ <strong
+ class="text-danger-800 gl-pl-4"
+ >
+
+ 1 Critical
+
+ </strong>
+ </span>
+
+ <span>
+ <span
+ class="gl-px-2"
+ >
+
+ 0 High
+
+ </span>
+ </span>
+ and
+ <span>
+ <span
+ class="gl-px-2"
+ >
+
+ 0 Others
+
+ </span>
+ </span>
+ </span>
+</span>
+`;
+
+exports[`SecuritySummary component given the message {"countMessage": "%{criticalStart}1 Critical%{criticalEnd} %{highStart}2 High%{highEnd} and %{otherStart}0 Others%{otherEnd}", "critical": 1, "high": 2, "message": "Security scanning detected %{totalStart}3%{totalEnd} potential vulnerabilities", "other": 0, "status": "", "total": 3} interpolates correctly 1`] = `
+<span>
+ Security scanning detected
+ <strong>
+ 3
+ </strong>
+ potential vulnerabilities
+ <span
+ class="gl-font-sm"
+ >
+ <span>
+ <strong
+ class="text-danger-800 gl-pl-4"
+ >
+
+ 1 Critical
+
+ </strong>
+ </span>
+
+ <span>
+ <strong
+ class="text-danger-600 gl-px-2"
+ >
+
+ 2 High
+
+ </strong>
+ </span>
+ and
+ <span>
+ <span
+ class="gl-px-2"
+ >
+
+ 0 Others
+
+ </span>
+ </span>
+ </span>
+</span>
+`;
+
+exports[`SecuritySummary component given the message {"message": ""} interpolates correctly 1`] = `
+<span>
+
+ <!---->
+</span>
+`;
+
+exports[`SecuritySummary component given the message {"message": "foo"} interpolates correctly 1`] = `
+<span>
+ foo
+ <!---->
+</span>
+`;
diff --git a/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js b/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js
new file mode 100644
index 00000000000..60203493cbd
--- /dev/null
+++ b/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js
@@ -0,0 +1,68 @@
+import { GlLink, GlPopover } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue';
+
+const helpPath = '/docs';
+const discoverProjectSecurityPath = '/discoverProjectSecurityPath';
+
+describe('HelpIcon component', () => {
+ let wrapper;
+
+ const createWrapper = props => {
+ wrapper = shallowMount(HelpIcon, {
+ propsData: {
+ helpPath,
+ ...props,
+ },
+ });
+ };
+
+ const findLink = () => wrapper.find(GlLink);
+ const findPopover = () => wrapper.find(GlPopover);
+ const findPopoverTarget = () => wrapper.find({ ref: 'discoverProjectSecurity' });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('given a help path only', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('does not render a popover', () => {
+ expect(findPopover().exists()).toBe(false);
+ });
+
+ it('renders a help link', () => {
+ expect(findLink().attributes()).toMatchObject({
+ href: helpPath,
+ target: '_blank',
+ });
+ });
+ });
+
+ describe('given a help path and discover project security path', () => {
+ beforeEach(() => {
+ createWrapper({ discoverProjectSecurityPath });
+ });
+
+ it('renders a popover', () => {
+ const popover = findPopover();
+ expect(popover.props('target')()).toBe(findPopoverTarget().element);
+ expect(popover.attributes()).toMatchObject({
+ title: HelpIcon.i18n.upgradeToManageVulnerabilities,
+ triggers: 'click blur',
+ });
+ expect(popover.text()).toContain(HelpIcon.i18n.upgradeToInteract);
+ });
+
+ it('renders a link to the discover path', () => {
+ expect(findLink().attributes()).toMatchObject({
+ href: discoverProjectSecurityPath,
+ target: '_blank',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/security_reports/security_summary_spec.js b/spec/frontend/vue_shared/components/security_reports/security_summary_spec.js
new file mode 100644
index 00000000000..e57152c3cbf
--- /dev/null
+++ b/spec/frontend/vue_shared/components/security_reports/security_summary_spec.js
@@ -0,0 +1,38 @@
+import { GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import SecuritySummary from '~/vue_shared/security_reports/components/security_summary.vue';
+import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils';
+
+describe('SecuritySummary component', () => {
+ let wrapper;
+
+ const createWrapper = message => {
+ wrapper = shallowMount(SecuritySummary, {
+ propsData: { message },
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe.each([
+ { message: '' },
+ { message: 'foo' },
+ groupedTextBuilder({ reportType: 'Security scanning', critical: 1, high: 0, total: 1 }),
+ groupedTextBuilder({ reportType: 'Security scanning', critical: 0, high: 1, total: 1 }),
+ groupedTextBuilder({ reportType: 'Security scanning', critical: 1, high: 2, total: 3 }),
+ ])('given the message %p', message => {
+ beforeEach(() => {
+ createWrapper(message);
+ });
+
+ it('interpolates correctly', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
index 9db86fa775f..596cb22fca5 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
@@ -33,8 +33,8 @@ describe('BaseComponent', () => {
expect(vm.hiddenInputName).toBe('issue[label_names][]');
});
- it('returns correct string when showCreate prop is `false`', () => {
- wrapper.setProps({ showCreate: false });
+ it('returns correct string when showCreate prop is `false`', async () => {
+ await wrapper.setProps({ showCreate: false });
expect(vm.hiddenInputName).toBe('label_id[]');
});
@@ -45,8 +45,8 @@ describe('BaseComponent', () => {
expect(vm.createLabelTitle).toBe('Create project label');
});
- it('return `Create group label` when `isProject` prop is false', () => {
- wrapper.setProps({ isProject: false });
+ it('return `Create group label` when `isProject` prop is false', async () => {
+ await wrapper.setProps({ isProject: false });
expect(vm.createLabelTitle).toBe('Create group label');
});
@@ -57,8 +57,8 @@ describe('BaseComponent', () => {
expect(vm.manageLabelsTitle).toBe('Manage project labels');
});
- it('return `Manage group labels` when `isProject` prop is false', () => {
- wrapper.setProps({ isProject: false });
+ it('return `Manage group labels` when `isProject` prop is false', async () => {
+ await wrapper.setProps({ isProject: false });
expect(vm.manageLabelsTitle).toBe('Manage group labels');
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
index 8c17a974b39..1206450bbeb 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
@@ -20,22 +20,24 @@ jest.mock('~/lib/utils/common_utils', () => ({
const localVue = createLocalVue();
localVue.use(Vuex);
-const createComponent = (config = mockConfig, slots = {}) =>
- shallowMount(LabelsSelectRoot, {
- localVue,
- slots,
- store: new Vuex.Store(labelsSelectModule()),
- propsData: config,
- stubs: {
- 'dropdown-contents': DropdownContents,
- },
- });
-
describe('LabelsSelectRoot', () => {
let wrapper;
+ let store;
+
+ const createComponent = (config = mockConfig, slots = {}) => {
+ wrapper = shallowMount(LabelsSelectRoot, {
+ localVue,
+ slots,
+ store,
+ propsData: config,
+ stubs: {
+ 'dropdown-contents': DropdownContents,
+ },
+ });
+ };
beforeEach(() => {
- wrapper = createComponent();
+ store = new Vuex.Store(labelsSelectModule());
});
afterEach(() => {
@@ -45,6 +47,7 @@ describe('LabelsSelectRoot', () => {
describe('methods', () => {
describe('handleVuexActionDispatch', () => {
it('calls `handleDropdownClose` when params `action.type` is `toggleDropdownContents` and state has `showDropdownButton` & `showDropdownContents` props `false`', () => {
+ createComponent();
jest.spyOn(wrapper.vm, 'handleDropdownClose').mockImplementation();
wrapper.vm.handleVuexActionDispatch(
@@ -67,7 +70,7 @@ describe('LabelsSelectRoot', () => {
});
it('calls `handleDropdownClose` with state.labels filterd using `set` prop when dropdown variant is `embedded`', () => {
- wrapper = createComponent({
+ createComponent({
...mockConfig,
variant: 'embedded',
});
@@ -95,6 +98,10 @@ describe('LabelsSelectRoot', () => {
});
describe('handleDropdownClose', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
it('emits `updateSelectedLabels` & `onDropdownClose` events on component when provided `labels` param is not empty', () => {
wrapper.vm.handleDropdownClose([{ id: 1 }, { id: 2 }]);
@@ -112,6 +119,7 @@ describe('LabelsSelectRoot', () => {
describe('handleCollapsedValueClick', () => {
it('emits `toggleCollapse` event on component', () => {
+ createComponent();
wrapper.vm.handleCollapsedValueClick();
expect(wrapper.emitted().toggleCollapse).toBeTruthy();
@@ -121,6 +129,7 @@ describe('LabelsSelectRoot', () => {
describe('template', () => {
it('renders component with classes `labels-select-wrapper position-relative`', () => {
+ createComponent();
expect(wrapper.attributes('class')).toContain('labels-select-wrapper position-relative');
});
@@ -131,7 +140,7 @@ describe('LabelsSelectRoot', () => {
`(
'renders component root element with CSS class `$cssClass` when `state.variant` is "$variant"',
({ variant, cssClass }) => {
- wrapper = createComponent({
+ createComponent({
...mockConfig,
variant,
});
@@ -142,57 +151,58 @@ describe('LabelsSelectRoot', () => {
},
);
- it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', () => {
+ it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', async () => {
+ createComponent();
+ await wrapper.vm.$nextTick;
expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true);
});
- it('renders `dropdown-title` component', () => {
+ it('renders `dropdown-title` component', async () => {
+ createComponent();
+ await wrapper.vm.$nextTick;
expect(wrapper.find(DropdownTitle).exists()).toBe(true);
});
- it('renders `dropdown-value` component', () => {
- const wrapperDropdownValue = createComponent(mockConfig, {
+ it('renders `dropdown-value` component', async () => {
+ createComponent(mockConfig, {
default: 'None',
});
+ await wrapper.vm.$nextTick;
- return wrapperDropdownValue.vm.$nextTick(() => {
- const valueComp = wrapperDropdownValue.find(DropdownValue);
+ const valueComp = wrapper.find(DropdownValue);
- expect(valueComp.exists()).toBe(true);
- expect(valueComp.text()).toBe('None');
-
- wrapperDropdownValue.destroy();
- });
+ expect(valueComp.exists()).toBe(true);
+ expect(valueComp.text()).toBe('None');
});
- it('renders `dropdown-button` component when `showDropdownButton` prop is `true`', () => {
+ it('renders `dropdown-button` component when `showDropdownButton` prop is `true`', async () => {
+ createComponent();
wrapper.vm.$store.dispatch('toggleDropdownButton');
-
+ await wrapper.vm.$nextTick;
expect(wrapper.find(DropdownButton).exists()).toBe(true);
});
- it('renders `dropdown-contents` component when `showDropdownButton` & `showDropdownContents` prop is `true`', () => {
+ it('renders `dropdown-contents` component when `showDropdownButton` & `showDropdownContents` prop is `true`', async () => {
+ createComponent();
wrapper.vm.$store.dispatch('toggleDropdownContents');
-
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(DropdownContents).exists()).toBe(true);
- });
+ await wrapper.vm.$nextTick;
+ expect(wrapper.find(DropdownContents).exists()).toBe(true);
});
describe('sets content direction based on viewport', () => {
- it('does not set direction when `state.variant` is not "embedded"', () => {
- wrapper.vm.$store.dispatch('toggleDropdownContents');
+ it('does not set direction when `state.variant` is not "embedded"', async () => {
+ createComponent();
+ wrapper.vm.$store.dispatch('toggleDropdownContents');
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
+ await wrapper.vm.$nextTick;
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
- });
+ expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
});
describe('when `state.variant` is "embedded"', () => {
beforeEach(() => {
- wrapper = createComponent({ ...mockConfig, variant: 'embedded' });
+ createComponent({ ...mockConfig, variant: 'embedded' });
wrapper.vm.$store.dispatch('toggleDropdownContents');
});
@@ -216,4 +226,22 @@ describe('LabelsSelectRoot', () => {
});
});
});
+
+ it('calls toggleDropdownContents action when isEditing prop is changing to true', async () => {
+ createComponent();
+
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+ await wrapper.setProps({ isEditing: true });
+
+ expect(store.dispatch).toHaveBeenCalledWith('toggleDropdownContents');
+ });
+
+ it('does not call toggleDropdownContents action when isEditing prop is changing to false', async () => {
+ createComponent();
+
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+ await wrapper.setProps({ isEditing: false });
+
+ expect(store.dispatch).not.toHaveBeenCalled();
+ });
});
diff --git a/spec/frontend/vue_shared/components/toggle_button_spec.js b/spec/frontend/vue_shared/components/toggle_button_spec.js
index f58647ff12b..2822b1999bc 100644
--- a/spec/frontend/vue_shared/components/toggle_button_spec.js
+++ b/spec/frontend/vue_shared/components/toggle_button_spec.js
@@ -1,101 +1,96 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import toggleButton from '~/vue_shared/components/toggle_button.vue';
+import { shallowMount } from '@vue/test-utils';
+import { GlIcon } from '@gitlab/ui';
+import ToggleButton from '~/vue_shared/components/toggle_button.vue';
-describe('Toggle Button', () => {
- let vm;
- let Component;
+describe('Toggle Button component', () => {
+ let wrapper;
- beforeEach(() => {
- Component = Vue.extend(toggleButton);
- });
+ function createComponent(propsData = {}) {
+ wrapper = shallowMount(ToggleButton, {
+ propsData,
+ });
+ }
+
+ const findInput = () => wrapper.find('input');
+ const findButton = () => wrapper.find('button');
+ const findToggleIcon = () => wrapper.find(GlIcon);
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
- describe('render output', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
- value: true,
- name: 'foo',
- });
- });
-
- it('renders input with provided name', () => {
- expect(vm.$el.querySelector('input').getAttribute('name')).toEqual('foo');
+ it('renders input with provided name', () => {
+ createComponent({
+ name: 'foo',
});
- it('renders input with provided value', () => {
- expect(vm.$el.querySelector('input').getAttribute('value')).toEqual('true');
- });
-
- it('renders input status icon', () => {
- expect(vm.$el.querySelectorAll('span.toggle-icon').length).toEqual(1);
- expect(vm.$el.querySelectorAll('svg.s18').length).toEqual(1);
- });
+ expect(findInput().attributes('name')).toBe('foo');
});
- describe('is-checked', () => {
+ describe.each`
+ value | iconName
+ ${true} | ${'status_success_borderless'}
+ ${false} | ${'status_failed_borderless'}
+ `('when `value` prop is `$value`', ({ value, iconName }) => {
beforeEach(() => {
- vm = mountComponent(Component, {
- value: true,
+ createComponent({
+ value,
+ name: 'foo',
});
-
- jest.spyOn(vm, '$emit').mockImplementation(() => {});
});
- it('renders is checked class', () => {
- expect(vm.$el.querySelector('button').classList.contains('is-checked')).toEqual(true);
+ it('renders input with correct value attribute', () => {
+ expect(findInput().attributes('value')).toBe(`${value}`);
});
- it('sets aria-label representing toggle state', () => {
- vm.value = true;
-
- expect(vm.ariaLabel).toEqual('Toggle Status: ON');
-
- vm.value = false;
-
- expect(vm.ariaLabel).toEqual('Toggle Status: OFF');
+ it('renders correct icon', () => {
+ const icon = findToggleIcon();
+ expect(icon.isVisible()).toBe(true);
+ expect(icon.props('name')).toBe(iconName);
+ expect(findButton().classes('is-checked')).toBe(value);
});
- it('emits change event when clicked', () => {
- vm.$el.querySelector('button').click();
+ describe('when clicked', () => {
+ it('emits `change` event with correct event', async () => {
+ findButton().trigger('click');
+ await wrapper.vm.$nextTick();
- expect(vm.$emit).toHaveBeenCalledWith('change', false);
+ expect(wrapper.emitted('change')).toStrictEqual([[!value]]);
+ });
});
});
- describe('is-disabled', () => {
+ describe('when `disabledInput` prop is `true`', () => {
beforeEach(() => {
- vm = mountComponent(Component, {
+ createComponent({
value: true,
disabledInput: true,
});
- jest.spyOn(vm, '$emit').mockImplementation(() => {});
});
it('renders disabled button', () => {
- expect(vm.$el.querySelector('button').classList.contains('is-disabled')).toEqual(true);
+ expect(findButton().classes()).toContain('is-disabled');
});
- it('does not emit change event when clicked', () => {
- vm.$el.querySelector('button').click();
+ it('does not emit change event when clicked', async () => {
+ findButton().trigger('click');
+ await wrapper.vm.$nextTick();
- expect(vm.$emit).not.toHaveBeenCalled();
+ expect(wrapper.emitted('change')).toBeFalsy();
});
});
- describe('is-loading', () => {
+ describe('when `isLoading` prop is `true`', () => {
beforeEach(() => {
- vm = mountComponent(Component, {
+ createComponent({
value: true,
isLoading: true,
});
});
it('renders loading class', () => {
- expect(vm.$el.querySelector('button').classList.contains('is-loading')).toEqual(true);
+ expect(findButton().classes()).toContain('is-loading');
});
});
});
diff --git a/spec/javascripts/vue_shared/components/tooltip_on_truncate_browser_spec.js b/spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js
index da964a2d5b9..175abf5aae0 100644
--- a/spec/javascripts/vue_shared/components/tooltip_on_truncate_browser_spec.js
+++ b/spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js
@@ -1,18 +1,16 @@
-// this file can't be migrated to jest because it relies on the browser to perform integration tests:
-// (specifically testing around css properties `overflow` and `white-space`)
-// see: https://gitlab.com/groups/gitlab-org/-/epics/895#what-if-theres-a-karma-spec-which-is-simply-unmovable-to-jest-ie-it-is-dependent-on-a-running-browser-environment
-
import { mount, shallowMount } from '@vue/test-utils';
+import { hasHorizontalOverflow } from '~/lib/utils/dom_utils';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
-const TEXT_SHORT = 'lorem';
-const TEXT_LONG = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do';
+const DUMMY_TEXT = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do';
-const TEXT_TRUNCATE = 'white-space: nowrap; overflow:hidden;';
-const STYLE_NORMAL = `${TEXT_TRUNCATE} display: inline-block; max-width: 1000px;`; // does not overflows
-const STYLE_OVERFLOWED = `${TEXT_TRUNCATE} display: inline-block; max-width: 50px;`; // overflowed when text is long
+const createChildElement = () => `<a href="#">${DUMMY_TEXT}</a>`;
-const createElementWithStyle = (style, content) => `<a href="#" style="${style}">${content}</a>`;
+jest.mock('~/lib/utils/dom_utils', () => ({
+ hasHorizontalOverflow: jest.fn(() => {
+ throw new Error('this needs to be mocked');
+ }),
+}));
describe('TooltipOnTruncate component', () => {
let wrapper;
@@ -24,9 +22,6 @@ describe('TooltipOnTruncate component', () => {
propsData: {
...propsData,
},
- attrs: {
- style: STYLE_OVERFLOWED,
- },
...options,
});
};
@@ -39,7 +34,7 @@ describe('TooltipOnTruncate component', () => {
title: { default: '' },
},
template: `
- <TooltipOnTruncate :title="title" truncate-target="child" style="${STYLE_OVERFLOWED}">
+ <TooltipOnTruncate :title="title" truncate-target="child">
<div>{{title}}</div>
</TooltipOnTruncate>
`,
@@ -65,33 +60,37 @@ describe('TooltipOnTruncate component', () => {
describe('with default target', () => {
it('renders tooltip if truncated', () => {
+ hasHorizontalOverflow.mockReturnValueOnce(true);
createComponent({
propsData: {
- title: TEXT_LONG,
+ title: DUMMY_TEXT,
},
slots: {
- default: [TEXT_LONG],
+ default: [DUMMY_TEXT],
},
});
return wrapper.vm.$nextTick().then(() => {
+ expect(hasHorizontalOverflow).toHaveBeenCalledWith(wrapper.element);
expect(hasTooltip()).toBe(true);
- expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
+ expect(wrapper.attributes('data-original-title')).toEqual(DUMMY_TEXT);
expect(wrapper.attributes('data-placement')).toEqual('top');
});
});
it('does not render tooltip if normal', () => {
+ hasHorizontalOverflow.mockReturnValueOnce(false);
createComponent({
propsData: {
- title: TEXT_SHORT,
+ title: DUMMY_TEXT,
},
slots: {
- default: [TEXT_SHORT],
+ default: [DUMMY_TEXT],
},
});
return wrapper.vm.$nextTick().then(() => {
+ expect(hasHorizontalOverflow).toHaveBeenCalledWith(wrapper.element);
expect(hasTooltip()).toBe(false);
});
});
@@ -99,35 +98,36 @@ describe('TooltipOnTruncate component', () => {
describe('with child target', () => {
it('renders tooltip if truncated', () => {
+ hasHorizontalOverflow.mockReturnValueOnce(true);
createComponent({
- attrs: {
- style: STYLE_NORMAL,
- },
propsData: {
- title: TEXT_LONG,
+ title: DUMMY_TEXT,
truncateTarget: 'child',
},
slots: {
- default: createElementWithStyle(STYLE_OVERFLOWED, TEXT_LONG),
+ default: createChildElement(),
},
});
return wrapper.vm.$nextTick().then(() => {
+ expect(hasHorizontalOverflow).toHaveBeenCalledWith(wrapper.element.childNodes[0]);
expect(hasTooltip()).toBe(true);
});
});
it('does not render tooltip if normal', () => {
+ hasHorizontalOverflow.mockReturnValueOnce(false);
createComponent({
propsData: {
truncateTarget: 'child',
},
slots: {
- default: createElementWithStyle(STYLE_NORMAL, TEXT_LONG),
+ default: createChildElement(),
},
});
return wrapper.vm.$nextTick().then(() => {
+ expect(hasHorizontalOverflow).toHaveBeenCalledWith(wrapper.element.childNodes[0]);
expect(hasTooltip()).toBe(false);
});
});
@@ -135,23 +135,19 @@ describe('TooltipOnTruncate component', () => {
describe('with fn target', () => {
it('renders tooltip if truncated', () => {
+ hasHorizontalOverflow.mockReturnValueOnce(true);
createComponent({
- attrs: {
- style: STYLE_NORMAL,
- },
propsData: {
- title: TEXT_LONG,
+ title: DUMMY_TEXT,
truncateTarget: el => el.childNodes[1],
},
slots: {
- default: [
- createElementWithStyle('', TEXT_LONG),
- createElementWithStyle(STYLE_OVERFLOWED, TEXT_LONG),
- ],
+ default: [createChildElement(), createChildElement()],
},
});
return wrapper.vm.$nextTick().then(() => {
+ expect(hasHorizontalOverflow).toHaveBeenCalledWith(wrapper.element.childNodes[1]);
expect(hasTooltip()).toBe(true);
});
});
@@ -161,15 +157,13 @@ describe('TooltipOnTruncate component', () => {
it('sets data-placement when tooltip is rendered', () => {
const placement = 'bottom';
+ hasHorizontalOverflow.mockReturnValueOnce(true);
createComponent({
propsData: {
placement,
},
- attrs: {
- style: STYLE_OVERFLOWED,
- },
slots: {
- default: TEXT_LONG,
+ default: DUMMY_TEXT,
},
});
@@ -183,21 +177,23 @@ describe('TooltipOnTruncate component', () => {
describe('updates when title and slot content changes', () => {
describe('is initialized with a long text', () => {
beforeEach(() => {
+ hasHorizontalOverflow.mockReturnValueOnce(true);
createWrappedComponent({
- propsData: { title: TEXT_LONG },
+ propsData: { title: DUMMY_TEXT },
});
return parent.vm.$nextTick();
});
it('renders tooltip', () => {
expect(hasTooltip()).toBe(true);
- expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
+ expect(wrapper.attributes('data-original-title')).toEqual(DUMMY_TEXT);
expect(wrapper.attributes('data-placement')).toEqual('top');
});
it('does not render tooltip after updated to a short text', () => {
+ hasHorizontalOverflow.mockReturnValueOnce(false);
parent.setProps({
- title: TEXT_SHORT,
+ title: 'new-text',
});
return wrapper.vm
@@ -211,8 +207,9 @@ describe('TooltipOnTruncate component', () => {
describe('is initialized with a short text', () => {
beforeEach(() => {
+ hasHorizontalOverflow.mockReturnValueOnce(false);
createWrappedComponent({
- propsData: { title: TEXT_SHORT },
+ propsData: { title: DUMMY_TEXT },
});
return wrapper.vm.$nextTick();
});
@@ -221,9 +218,11 @@ describe('TooltipOnTruncate component', () => {
expect(hasTooltip()).toBe(false);
});
- it('renders tooltip after updated to a long text', () => {
+ it('renders tooltip after text is updated', () => {
+ hasHorizontalOverflow.mockReturnValueOnce(true);
+ const newText = 'new-text';
parent.setProps({
- title: TEXT_LONG,
+ title: newText,
});
return wrapper.vm
@@ -231,7 +230,7 @@ describe('TooltipOnTruncate component', () => {
.then(() => wrapper.vm.$nextTick()) // wait 2 times to get an updated slot
.then(() => {
expect(hasTooltip()).toBe(true);
- expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
+ expect(wrapper.attributes('data-original-title')).toEqual(newText);
expect(wrapper.attributes('data-placement')).toEqual('top');
});
});
diff --git a/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js b/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js
new file mode 100644
index 00000000000..7e70407655a
--- /dev/null
+++ b/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js
@@ -0,0 +1,64 @@
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
+
+describe('SecurityReportDownloadDropdown component', () => {
+ let wrapper;
+ let artifacts;
+
+ const createComponent = props => {
+ wrapper = shallowMount(SecurityReportDownloadDropdown, {
+ propsData: { ...props },
+ });
+ };
+
+ const findDropdown = () => wrapper.find(GlDropdown);
+ const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('given report artifacts', () => {
+ beforeEach(() => {
+ artifacts = [
+ {
+ name: 'foo',
+ path: '/foo.json',
+ },
+ {
+ name: 'bar',
+ path: '/bar.json',
+ },
+ ];
+
+ createComponent({ artifacts });
+ });
+
+ it('renders a dropdown', () => {
+ expect(findDropdown().props('loading')).toBe(false);
+ });
+
+ it('renders a dropdown items for each artifact', () => {
+ artifacts.forEach((artifact, i) => {
+ const item = findDropdownItems().at(i);
+ expect(item.text()).toContain(artifact.name);
+ expect(item.attributes()).toMatchObject({
+ href: artifact.path,
+ download: expect.any(String),
+ });
+ });
+ });
+ });
+
+ describe('given it is loading', () => {
+ beforeEach(() => {
+ createComponent({ artifacts: [], loading: true });
+ });
+
+ it('renders a loading dropdown', () => {
+ expect(findDropdown().props('loading')).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/security_reports/mock_data.js b/spec/frontend/vue_shared/security_reports/mock_data.js
new file mode 100644
index 00000000000..e93ca8329e7
--- /dev/null
+++ b/spec/frontend/vue_shared/security_reports/mock_data.js
@@ -0,0 +1,437 @@
+import {
+ REPORT_TYPE_SAST,
+ REPORT_TYPE_SECRET_DETECTION,
+} from '~/vue_shared/security_reports/constants';
+
+export const mockFindings = [
+ {
+ id: null,
+ report_type: 'dependency_scanning',
+ name: 'Cross-site Scripting in serialize-javascript',
+ severity: 'critical',
+ scanner: {
+ external_id: 'gemnasium',
+ name: 'Gemnasium',
+ version: '1.1.1',
+ url: 'https://gitlab.com/gitlab-org/security-products/gemnasium',
+ },
+ identifiers: [
+ {
+ external_type: 'gemnasium',
+ external_id: '58caa017-9a9a-46d6-bab2-ec930f46833c',
+ name: 'Gemnasium-58caa017-9a9a-46d6-bab2-ec930f46833c',
+ url:
+ 'https://deps.sec.gitlab.com/packages/npm/serialize-javascript/versions/1.7.0/advisories',
+ },
+ {
+ external_type: 'cve',
+ external_id: 'CVE-2019-16769',
+ name: 'CVE-2019-16769',
+ url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16769',
+ },
+ ],
+ project_fingerprint: '09df9f4d11c8deb93d81bdcc39f7667b44143298',
+ create_vulnerability_feedback_issue_path: '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ create_vulnerability_feedback_merge_request_path:
+ '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ create_vulnerability_feedback_dismissal_path: '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ project: {
+ id: 7071551,
+ name: 'gitlab-ui',
+ full_path: '/gitlab-org/gitlab-ui',
+ full_name: 'GitLab.org / gitlab-ui',
+ },
+ dismissal_feedback: null,
+ issue_feedback: null,
+ merge_request_feedback: null,
+ description:
+ 'The serialize-javascript npm package is vulnerable to Cross-site Scripting (XSS). It does not properly mitigate against unsafe characters in serialized regular expressions. If serialized data of regular expression objects are used in an environment other than Node.js, it is affected by this vulnerability.',
+ links: [{ url: 'https://nvd.nist.gov/vuln/detail/CVE-2019-16769' }],
+ location: {
+ file: 'yarn.lock',
+ dependency: { package: { name: 'serialize-javascript' }, version: '1.7.0' },
+ },
+ remediations: [null],
+ solution: 'Upgrade to version 2.1.1 or above.',
+ state: 'opened',
+ blob_path: '/gitlab-org/gitlab-ui/blob/ad137f0a8ac59af961afe47d04e5cc062c6864a9/yarn.lock',
+ evidence: 'Credit Card Detected: Diners Card',
+ },
+ {
+ id: null,
+ report_type: 'dependency_scanning',
+ name: '3rd party CORS request may execute in jquery',
+ severity: 'high',
+ scanner: { external_id: 'retire.js', name: 'Retire.js' },
+ identifiers: [
+ {
+ external_type: 'cve',
+ external_id: 'CVE-2015-9251',
+ name: 'CVE-2015-9251',
+ url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-9251',
+ },
+ ],
+ project_fingerprint: '1ecd3b214cf39c0b9ad23a0a9679778d7cf55876',
+ create_vulnerability_feedback_issue_path: '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ create_vulnerability_feedback_merge_request_path:
+ '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ create_vulnerability_feedback_dismissal_path: '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ project: {
+ id: 7071551,
+ name: 'gitlab-ui',
+ full_path: '/gitlab-org/gitlab-ui',
+ full_name: 'GitLab.org / gitlab-ui',
+ },
+ dismissal_feedback: {
+ id: 2528,
+ created_at: '2019-08-26T12:30:32.349Z',
+ project_id: 7071551,
+ author: {
+ id: 181229,
+ name: "Lukas 'Eipi' Eipert",
+ username: 'leipert',
+ state: 'active',
+ avatar_url:
+ 'https://secure.gravatar.com/avatar/19a1f1260fa70323f35bc508927921a2?s=80\u0026d=identicon',
+ web_url: 'https://gitlab.com/leipert',
+ status_tooltip_html: null,
+ path: '/leipert',
+ },
+ comment_details: {
+ comment: 'This particular jQuery version appears in a test path of tinycolor2.\n',
+ comment_timestamp: '2019-08-26T12:30:37.610Z',
+ comment_author: {
+ id: 181229,
+ name: "Lukas 'Eipi' Eipert",
+ username: 'leipert',
+ state: 'active',
+ avatar_url:
+ 'https://secure.gravatar.com/avatar/19a1f1260fa70323f35bc508927921a2?s=80\u0026d=identicon',
+ web_url: 'https://gitlab.com/leipert',
+ status_tooltip_html: null,
+ path: '/leipert',
+ },
+ },
+ pipeline: { id: 78375355, path: '/gitlab-org/gitlab-ui/pipelines/78375355' },
+ destroy_vulnerability_feedback_dismissal_path:
+ '/gitlab-org/gitlab-ui/vulnerability_feedback/2528',
+ category: 'dependency_scanning',
+ feedback_type: 'dismissal',
+ branch: 'leipert-dogfood-secure',
+ project_fingerprint: '1ecd3b214cf39c0b9ad23a0a9679778d7cf55876',
+ },
+ issue_feedback: null,
+ merge_request_feedback: null,
+ description: null,
+ links: [
+ { url: 'https://github.com/jquery/jquery/issues/2432' },
+ { url: 'http://blog.jquery.com/2016/01/08/jquery-2-2-and-1-12-released/' },
+ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2015-9251' },
+ { url: 'http://research.insecurelabs.org/jquery/test/' },
+ ],
+ location: {
+ file: 'node_modules/tinycolor2/demo/jquery-1.9.1.js',
+ dependency: { package: { name: 'jquery' }, version: '1.9.1' },
+ },
+ remediations: [null],
+ solution: null,
+ state: 'dismissed',
+ blob_path:
+ '/gitlab-org/gitlab-ui/blob/ad137f0a8ac59af961afe47d04e5cc062c6864a9/node_modules/tinycolor2/demo/jquery-1.9.1.js',
+ },
+ {
+ id: null,
+ report_type: 'dependency_scanning',
+ name:
+ 'jQuery before 3.4.0, as used in Drupal, Backdrop CMS, and other products, mishandles jQuery.extend(true, {}, ...) because of Object.prototype pollution in jquery',
+ severity: 'low',
+ scanner: { external_id: 'retire.js', name: 'Retire.js' },
+ identifiers: [
+ {
+ external_type: 'cve',
+ external_id: 'CVE-2019-11358',
+ name: 'CVE-2019-11358',
+ url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11358',
+ },
+ ],
+ project_fingerprint: 'aeb4b2442d92d0ccf7023f0c220bda8b4ba910e3',
+ create_vulnerability_feedback_issue_path: '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ create_vulnerability_feedback_merge_request_path:
+ '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ create_vulnerability_feedback_dismissal_path: '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ project: {
+ id: 7071551,
+ name: 'gitlab-ui',
+ full_path: '/gitlab-org/gitlab-ui',
+ full_name: 'GitLab.org / gitlab-ui',
+ },
+ dismissal_feedback: {
+ id: 4197,
+ created_at: '2019-11-14T11:03:18.472Z',
+ project_id: 7071551,
+ author: {
+ id: 181229,
+ name: "Lukas 'Eipi' Eipert",
+ username: 'leipert',
+ state: 'active',
+ avatar_url:
+ 'https://secure.gravatar.com/avatar/19a1f1260fa70323f35bc508927921a2?s=80\u0026d=identicon',
+ web_url: 'https://gitlab.com/leipert',
+ status_tooltip_html: null,
+ path: '/leipert',
+ },
+ comment_details: {
+ comment:
+ 'This is a false positive, as it just part of some documentation assets of sass-true.',
+ comment_timestamp: '2019-11-14T11:03:18.464Z',
+ comment_author: {
+ id: 181229,
+ name: "Lukas 'Eipi' Eipert",
+ username: 'leipert',
+ state: 'active',
+ avatar_url:
+ 'https://secure.gravatar.com/avatar/19a1f1260fa70323f35bc508927921a2?s=80\u0026d=identicon',
+ web_url: 'https://gitlab.com/leipert',
+ status_tooltip_html: null,
+ path: '/leipert',
+ },
+ },
+ destroy_vulnerability_feedback_dismissal_path:
+ '/gitlab-org/gitlab-ui/vulnerability_feedback/4197',
+ category: 'dependency_scanning',
+ feedback_type: 'dismissal',
+ branch: null,
+ project_fingerprint: 'aeb4b2442d92d0ccf7023f0c220bda8b4ba910e3',
+ },
+ issue_feedback: null,
+ merge_request_feedback: null,
+ description: null,
+ links: [
+ { url: 'https://blog.jquery.com/2019/04/10/jquery-3-4-0-released/' },
+ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2019-11358' },
+ { url: 'https://github.com/jquery/jquery/commit/753d591aea698e57d6db58c9f722cd0808619b1b' },
+ ],
+ location: {
+ file: 'node_modules/sass-true/docs/assets/webpack/common.min.js',
+ dependency: { package: { name: 'jquery' }, version: '3.3.1' },
+ },
+ remediations: [null],
+ solution: null,
+ state: 'dismissed',
+ blob_path:
+ '/gitlab-org/gitlab-ui/blob/ad137f0a8ac59af961afe47d04e5cc062c6864a9/node_modules/sass-true/docs/assets/webpack/common.min.js',
+ },
+ {
+ id: null,
+ report_type: 'dependency_scanning',
+ name:
+ 'jQuery before 3.4.0, as used in Drupal, Backdrop CMS, and other products, mishandles jQuery.extend(true, {}, ...) because of Object.prototype pollution in jquery',
+ severity: 'low',
+ scanner: { external_id: 'retire.js', name: 'Retire.js' },
+ identifiers: [
+ {
+ external_type: 'cve',
+ external_id: 'CVE-2019-11358',
+ name: 'CVE-2019-11358',
+ url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11358',
+ },
+ ],
+ project_fingerprint: 'eb86aa13eb9d897a083ead6e134aa78aa9cadd52',
+ create_vulnerability_feedback_issue_path: '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ create_vulnerability_feedback_merge_request_path:
+ '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ create_vulnerability_feedback_dismissal_path: '/gitlab-org/gitlab-ui/vulnerability_feedback',
+ project: {
+ id: 7071551,
+ name: 'gitlab-ui',
+ full_path: '/gitlab-org/gitlab-ui',
+ full_name: 'GitLab.org / gitlab-ui',
+ },
+ dismissal_feedback: {
+ id: 2527,
+ created_at: '2019-08-26T12:29:43.624Z',
+ project_id: 7071551,
+ author: {
+ id: 181229,
+ name: "Lukas 'Eipi' Eipert",
+ username: 'leipert',
+ state: 'active',
+ avatar_url:
+ 'https://secure.gravatar.com/avatar/19a1f1260fa70323f35bc508927921a2?s=80\u0026d=identicon',
+ web_url: 'https://gitlab.com/leipert',
+ status_tooltip_html: null,
+ path: '/leipert',
+ },
+ comment_details: {
+ comment: 'This particular jQuery version appears in a test path of tinycolor2.',
+ comment_timestamp: '2019-08-26T12:30:14.840Z',
+ comment_author: {
+ id: 181229,
+ name: "Lukas 'Eipi' Eipert",
+ username: 'leipert',
+ state: 'active',
+ avatar_url:
+ 'https://secure.gravatar.com/avatar/19a1f1260fa70323f35bc508927921a2?s=80\u0026d=identicon',
+ web_url: 'https://gitlab.com/leipert',
+ status_tooltip_html: null,
+ path: '/leipert',
+ },
+ },
+ pipeline: { id: 78375355, path: '/gitlab-org/gitlab-ui/pipelines/78375355' },
+ destroy_vulnerability_feedback_dismissal_path:
+ '/gitlab-org/gitlab-ui/vulnerability_feedback/2527',
+ category: 'dependency_scanning',
+ feedback_type: 'dismissal',
+ branch: 'leipert-dogfood-secure',
+ project_fingerprint: 'eb86aa13eb9d897a083ead6e134aa78aa9cadd52',
+ },
+ issue_feedback: null,
+ merge_request_feedback: null,
+ description: null,
+ links: [
+ { url: 'https://blog.jquery.com/2019/04/10/jquery-3-4-0-released/' },
+ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2019-11358' },
+ { url: 'https://github.com/jquery/jquery/commit/753d591aea698e57d6db58c9f722cd0808619b1b' },
+ ],
+ location: {
+ file: 'node_modules/tinycolor2/demo/jquery-1.9.1.js',
+ dependency: { package: { name: 'jquery' }, version: '1.9.1' },
+ },
+ remediations: [null],
+ solution: null,
+ state: 'dismissed',
+ blob_path:
+ '/gitlab-org/gitlab-ui/blob/ad137f0a8ac59af961afe47d04e5cc062c6864a9/node_modules/tinycolor2/demo/jquery-1.9.1.js',
+ },
+];
+
+export const sastDiffSuccessMock = {
+ added: [mockFindings[0]],
+ fixed: [mockFindings[1], mockFindings[2]],
+ existing: [mockFindings[3]],
+ base_report_created_at: '2020-01-01T10:00:00.000Z',
+ base_report_out_of_date: false,
+ head_report_created_at: '2020-01-10T10:00:00.000Z',
+};
+
+export const secretScanningDiffSuccessMock = {
+ added: [mockFindings[0], mockFindings[1]],
+ fixed: [mockFindings[2]],
+ base_report_created_at: '2020-01-01T10:00:00.000Z',
+ base_report_out_of_date: false,
+ head_report_created_at: '2020-01-10T10:00:00.000Z',
+};
+
+export const securityReportDownloadPathsQueryResponse = {
+ project: {
+ mergeRequest: {
+ headPipeline: {
+ id: 'gid://gitlab/Ci::Pipeline/176',
+ jobs: {
+ nodes: [
+ {
+ name: 'secret_detection',
+ artifacts: {
+ nodes: [
+ {
+ downloadPath:
+ '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=trace',
+ fileType: 'TRACE',
+ __typename: 'CiJobArtifact',
+ },
+ {
+ downloadPath:
+ '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=secret_detection',
+ fileType: 'SECRET_DETECTION',
+ __typename: 'CiJobArtifact',
+ },
+ ],
+ __typename: 'CiJobArtifactConnection',
+ },
+ __typename: 'CiJob',
+ },
+ {
+ name: 'bandit-sast',
+ artifacts: {
+ nodes: [
+ {
+ downloadPath:
+ '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=trace',
+ fileType: 'TRACE',
+ __typename: 'CiJobArtifact',
+ },
+ {
+ downloadPath:
+ '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=sast',
+ fileType: 'SAST',
+ __typename: 'CiJobArtifact',
+ },
+ ],
+ __typename: 'CiJobArtifactConnection',
+ },
+ __typename: 'CiJob',
+ },
+ {
+ name: 'eslint-sast',
+ artifacts: {
+ nodes: [
+ {
+ downloadPath:
+ '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=trace',
+ fileType: 'TRACE',
+ __typename: 'CiJobArtifact',
+ },
+ {
+ downloadPath:
+ '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=sast',
+ fileType: 'SAST',
+ __typename: 'CiJobArtifact',
+ },
+ ],
+ __typename: 'CiJobArtifactConnection',
+ },
+ __typename: 'CiJob',
+ },
+ ],
+ __typename: 'CiJobConnection',
+ },
+ __typename: 'Pipeline',
+ },
+ __typename: 'MergeRequest',
+ },
+ __typename: 'Project',
+ },
+};
+
+/**
+ * These correspond to SAST jobs in the securityReportDownloadPathsQueryResponse above.
+ */
+export const sastArtifacts = [
+ {
+ name: 'bandit-sast',
+ reportType: REPORT_TYPE_SAST,
+ path: '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=sast',
+ },
+ {
+ name: 'eslint-sast',
+ reportType: REPORT_TYPE_SAST,
+ path: '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=sast',
+ },
+];
+
+/**
+ * These correspond to Secret Detection jobs in the securityReportDownloadPathsQueryResponse above.
+ */
+export const secretDetectionArtifacts = [
+ {
+ name: 'secret_detection',
+ reportType: REPORT_TYPE_SECRET_DETECTION,
+ path:
+ '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=secret_detection',
+ },
+];
+
+export const expectedDownloadDropdownProps = {
+ loading: false,
+ artifacts: [...secretDetectionArtifacts, ...sastArtifacts],
+};
diff --git a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js
index ab87d80b291..c440081a0c4 100644
--- a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js
+++ b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js
@@ -1,162 +1,465 @@
-import { mount } from '@vue/test-utils';
+import { mount, createLocalVue } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import { merge } from 'lodash';
+import VueApollo from 'vue-apollo';
+import Vuex from 'vuex';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
+import { trimText } from 'helpers/text_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import {
+ expectedDownloadDropdownProps,
+ securityReportDownloadPathsQueryResponse,
+ sastDiffSuccessMock,
+ secretScanningDiffSuccessMock,
+} from 'jest/vue_shared/security_reports/mock_data';
import Api from '~/api';
-import Flash from '~/flash';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import {
+ REPORT_TYPE_SAST,
+ REPORT_TYPE_SECRET_DETECTION,
+} from '~/vue_shared/security_reports/constants';
+import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue';
+import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue';
+import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql';
jest.mock('~/flash');
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+const SAST_COMPARISON_PATH = '/sast.json';
+const SECRET_SCANNING_COMPARISON_PATH = '/secret_detection.json';
+
describe('Security reports app', () => {
let wrapper;
- let mrTabsMock;
const props = {
pipelineId: 123,
projectId: 456,
securityReportsDocsPath: '/docs',
+ discoverProjectSecurityPath: '/discoverProjectSecurityPath',
};
- const createComponent = () => {
- wrapper = mount(SecurityReportsApp, {
- propsData: { ...props },
- });
+ const createComponent = options => {
+ wrapper = mount(
+ SecurityReportsApp,
+ merge(
+ {
+ localVue,
+ propsData: { ...props },
+ stubs: {
+ HelpIcon: true,
+ },
+ },
+ options,
+ ),
+ );
+ };
+
+ const pendingHandler = () => new Promise(() => {});
+ const successHandler = () => Promise.resolve({ data: securityReportDownloadPathsQueryResponse });
+ const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] });
+ const createMockApolloProvider = handler => {
+ localVue.use(VueApollo);
+
+ const requestHandlers = [[securityReportDownloadPathsQuery, handler]];
+
+ return createMockApollo(requestHandlers);
};
const anyParams = expect.any(Object);
+ const findDownloadDropdown = () => wrapper.find(SecurityReportDownloadDropdown);
const findPipelinesTabAnchor = () => wrapper.find('[data-testid="show-pipelines"]');
- const findHelpLink = () => wrapper.find('[data-testid="help"]');
- const setupMrTabsMock = () => {
- mrTabsMock = { tabShown: jest.fn() };
- window.mrTabs = mrTabsMock;
- };
+ const findHelpIconComponent = () => wrapper.find(HelpIcon);
const setupMockJobArtifact = reportType => {
jest
.spyOn(Api, 'pipelineJobs')
.mockResolvedValue({ data: [{ artifacts: [{ file_type: reportType }] }] });
};
+ const expectPipelinesTabAnchor = () => {
+ const mrTabsMock = { tabShown: jest.fn() };
+ window.mrTabs = mrTabsMock;
+ findPipelinesTabAnchor().trigger('click');
+ expect(mrTabsMock.tabShown.mock.calls).toEqual([['pipelines']]);
+ };
afterEach(() => {
wrapper.destroy();
delete window.mrTabs;
});
- describe.each(SecurityReportsApp.reportTypes)('given a report type %p', reportType => {
- beforeEach(() => {
- window.mrTabs = { tabShown: jest.fn() };
- setupMockJobArtifact(reportType);
- createComponent();
- return wrapper.vm.$nextTick();
- });
+ describe.each([false, true])(
+ 'given the coreSecurityMrWidgetCounts feature flag is %p',
+ coreSecurityMrWidgetCounts => {
+ const createComponentWithFlag = options =>
+ createComponent(
+ merge(
+ {
+ provide: {
+ glFeatures: {
+ coreSecurityMrWidgetCounts,
+ },
+ },
+ },
+ options,
+ ),
+ );
- it('calls the pipelineJobs API correctly', () => {
- expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
- expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId, anyParams);
- });
+ describe.each(SecurityReportsApp.reportTypes)('given a report type %p', reportType => {
+ beforeEach(() => {
+ window.mrTabs = { tabShown: jest.fn() };
+ setupMockJobArtifact(reportType);
+ createComponentWithFlag();
+ return wrapper.vm.$nextTick();
+ });
- it('renders the expected message', () => {
- expect(wrapper.text()).toMatchInterpolatedText(SecurityReportsApp.i18n.scansHaveRun);
- });
+ it('calls the pipelineJobs API correctly', () => {
+ expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
+ expect(Api.pipelineJobs).toHaveBeenCalledWith(
+ props.projectId,
+ props.pipelineId,
+ anyParams,
+ );
+ });
- describe('clicking the anchor to the pipelines tab', () => {
- beforeEach(() => {
- setupMrTabsMock();
- findPipelinesTabAnchor().trigger('click');
+ it('renders the expected message', () => {
+ expect(wrapper.text()).toMatchInterpolatedText(
+ SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance,
+ );
+ });
+
+ describe('clicking the anchor to the pipelines tab', () => {
+ it('calls the mrTabs.tabShown global', () => {
+ expectPipelinesTabAnchor();
+ });
+ });
+
+ it('renders a help link', () => {
+ expect(findHelpIconComponent().props()).toEqual({
+ helpPath: props.securityReportsDocsPath,
+ discoverProjectSecurityPath: props.discoverProjectSecurityPath,
+ });
+ });
+ });
+
+ describe('given a report type "foo"', () => {
+ beforeEach(() => {
+ setupMockJobArtifact('foo');
+ createComponentWithFlag();
+ return wrapper.vm.$nextTick();
+ });
+
+ it('calls the pipelineJobs API correctly', () => {
+ expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
+ expect(Api.pipelineJobs).toHaveBeenCalledWith(
+ props.projectId,
+ props.pipelineId,
+ anyParams,
+ );
+ });
+
+ it('renders nothing', () => {
+ expect(wrapper.html()).toBe('');
+ });
});
- it('calls the mrTabs.tabShown global', () => {
- expect(mrTabsMock.tabShown.mock.calls).toEqual([['pipelines']]);
+ describe('security artifacts on last page of multi-page response', () => {
+ const numPages = 3;
+
+ beforeEach(() => {
+ jest
+ .spyOn(Api, 'pipelineJobs')
+ .mockImplementation(async (projectId, pipelineId, { page }) => {
+ const requestedPage = parseInt(page, 10);
+ if (requestedPage < numPages) {
+ return {
+ // Some jobs with no relevant artifacts
+ data: [{}, {}],
+ headers: { 'x-next-page': String(requestedPage + 1) },
+ };
+ } else if (requestedPage === numPages) {
+ return {
+ data: [{ artifacts: [{ file_type: SecurityReportsApp.reportTypes[0] }] }],
+ };
+ }
+
+ throw new Error('Test failed due to request of non-existent jobs page');
+ });
+
+ createComponentWithFlag();
+ return wrapper.vm.$nextTick();
+ });
+
+ it('fetches all pages', () => {
+ expect(Api.pipelineJobs).toHaveBeenCalledTimes(numPages);
+ });
+
+ it('renders the expected message', () => {
+ expect(wrapper.text()).toMatchInterpolatedText(
+ SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance,
+ );
+ });
});
- });
- it('renders a help link', () => {
- expect(findHelpLink().attributes()).toMatchObject({
- href: props.securityReportsDocsPath,
+ describe('given an error from the API', () => {
+ let error;
+
+ beforeEach(() => {
+ error = new Error('an error');
+ jest.spyOn(Api, 'pipelineJobs').mockRejectedValue(error);
+ createComponentWithFlag();
+ return wrapper.vm.$nextTick();
+ });
+
+ it('calls the pipelineJobs API correctly', () => {
+ expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
+ expect(Api.pipelineJobs).toHaveBeenCalledWith(
+ props.projectId,
+ props.pipelineId,
+ anyParams,
+ );
+ });
+
+ it('renders nothing', () => {
+ expect(wrapper.html()).toBe('');
+ });
+
+ it('calls createFlash correctly', () => {
+ expect(createFlash.mock.calls).toEqual([
+ [
+ {
+ message: SecurityReportsApp.i18n.apiError,
+ captureError: true,
+ error,
+ },
+ ],
+ ]);
+ });
});
- });
- });
+ },
+ );
+
+ describe('given the coreSecurityMrWidgetCounts feature flag is enabled', () => {
+ let mock;
+
+ const createComponentWithFlagEnabled = options =>
+ createComponent(
+ merge(options, {
+ provide: {
+ glFeatures: {
+ coreSecurityMrWidgetCounts: true,
+ },
+ },
+ }),
+ );
- describe('given a report type "foo"', () => {
beforeEach(() => {
- setupMockJobArtifact('foo');
- createComponent();
- return wrapper.vm.$nextTick();
+ mock = new MockAdapter(axios);
});
- it('calls the pipelineJobs API correctly', () => {
- expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
- expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId, anyParams);
+ afterEach(() => {
+ mock.restore();
});
- it('renders nothing', () => {
- expect(wrapper.html()).toBe('');
- });
+ const SAST_SUCCESS_MESSAGE =
+ 'Security scanning detected 1 potential vulnerability 1 Critical 0 High and 0 Others';
+ const SECRET_SCANNING_SUCCESS_MESSAGE =
+ 'Security scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others';
+ describe.each`
+ reportType | pathProp | path | successResponse | successMessage
+ ${REPORT_TYPE_SAST} | ${'sastComparisonPath'} | ${SAST_COMPARISON_PATH} | ${sastDiffSuccessMock} | ${SAST_SUCCESS_MESSAGE}
+ ${REPORT_TYPE_SECRET_DETECTION} | ${'secretScanningComparisonPath'} | ${SECRET_SCANNING_COMPARISON_PATH} | ${secretScanningDiffSuccessMock} | ${SECRET_SCANNING_SUCCESS_MESSAGE}
+ `(
+ 'given a $pathProp and $reportType artifact',
+ ({ reportType, pathProp, path, successResponse, successMessage }) => {
+ beforeEach(() => {
+ setupMockJobArtifact(reportType);
+ });
+
+ describe('when loading', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios, { delayResponse: 1 });
+ mock.onGet(path).replyOnce(200, successResponse);
+
+ createComponentWithFlagEnabled({
+ propsData: {
+ [pathProp]: path,
+ },
+ });
+
+ return waitForPromises();
+ });
+
+ it('should have loading message', () => {
+ expect(wrapper.text()).toBe('Security scanning is loading');
+ });
+
+ it('should not render the pipeline tab anchor', () => {
+ expect(findPipelinesTabAnchor().exists()).toBe(false);
+ });
+ });
+
+ describe('when successfully loaded', () => {
+ beforeEach(() => {
+ mock.onGet(path).replyOnce(200, successResponse);
+
+ createComponentWithFlagEnabled({
+ propsData: {
+ [pathProp]: path,
+ },
+ });
+
+ return waitForPromises();
+ });
+
+ it('should show counts', () => {
+ expect(trimText(wrapper.text())).toContain(successMessage);
+ });
+
+ it('should render the pipeline tab anchor', () => {
+ expectPipelinesTabAnchor();
+ });
+ });
+
+ describe('when an error occurs', () => {
+ beforeEach(() => {
+ mock.onGet(path).replyOnce(500);
+
+ createComponentWithFlagEnabled({
+ propsData: {
+ [pathProp]: path,
+ },
+ });
+
+ return waitForPromises();
+ });
+
+ it('should show error message', () => {
+ expect(trimText(wrapper.text())).toContain('Loading resulted in an error');
+ });
+
+ it('should render the pipeline tab anchor', () => {
+ expectPipelinesTabAnchor();
+ });
+ });
+ },
+ );
});
- describe('security artifacts on last page of multi-page response', () => {
- const numPages = 3;
+ describe('given coreSecurityMrWidgetDownloads feature flag is enabled', () => {
+ const createComponentWithFlagEnabled = options =>
+ createComponent(
+ merge(options, {
+ provide: {
+ glFeatures: {
+ coreSecurityMrWidgetDownloads: true,
+ },
+ },
+ }),
+ );
- beforeEach(() => {
- jest
- .spyOn(Api, 'pipelineJobs')
- .mockImplementation(async (projectId, pipelineId, { page }) => {
- const requestedPage = parseInt(page, 10);
- if (requestedPage < numPages) {
- return {
- // Some jobs with no relevant artifacts
- data: [{}, {}],
- headers: { 'x-next-page': String(requestedPage + 1) },
- };
- } else if (requestedPage === numPages) {
- return {
- data: [{ artifacts: [{ file_type: SecurityReportsApp.reportTypes[0] }] }],
- };
- }
-
- throw new Error('Test failed due to request of non-existent jobs page');
- });
-
- createComponent();
- return wrapper.vm.$nextTick();
+ describe('given the query is loading', () => {
+ beforeEach(() => {
+ createComponentWithFlagEnabled({
+ apolloProvider: createMockApolloProvider(pendingHandler),
+ });
+ });
+
+ // TODO: Remove this assertion as part of
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/273431
+ it('initially renders nothing', () => {
+ expect(wrapper.isEmpty()).toBe(true);
+ });
});
- it('fetches all pages', () => {
- expect(Api.pipelineJobs).toHaveBeenCalledTimes(numPages);
+ describe('given the query loads successfully', () => {
+ beforeEach(() => {
+ createComponentWithFlagEnabled({
+ apolloProvider: createMockApolloProvider(successHandler),
+ });
+ });
+
+ it('renders the download dropdown', () => {
+ expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
+ });
+
+ it('renders the expected message', () => {
+ const text = wrapper.text();
+ expect(text).not.toContain(SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance);
+ expect(text).toContain(SecurityReportsApp.i18n.scansHaveRun);
+ });
+
+ it('should not render the pipeline tab anchor', () => {
+ expect(findPipelinesTabAnchor().exists()).toBe(false);
+ });
});
- it('renders the expected message', () => {
- expect(wrapper.text()).toMatchInterpolatedText(SecurityReportsApp.i18n.scansHaveRun);
+ describe('given the query fails', () => {
+ beforeEach(() => {
+ createComponentWithFlagEnabled({
+ apolloProvider: createMockApolloProvider(failureHandler),
+ });
+ });
+
+ it('calls createFlash correctly', () => {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: SecurityReportsApp.i18n.apiError,
+ captureError: true,
+ error: expect.any(Error),
+ });
+ });
+
+ // TODO: Remove this assertion as part of
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/273431
+ it('renders nothing', () => {
+ expect(wrapper.isEmpty()).toBe(true);
+ });
});
});
- describe('given an error from the API', () => {
- let error;
+ describe('given coreSecurityMrWidgetCounts and coreSecurityMrWidgetDownloads feature flags are enabled', () => {
+ let mock;
beforeEach(() => {
- error = new Error('an error');
- jest.spyOn(Api, 'pipelineJobs').mockRejectedValue(error);
- createComponent();
- return wrapper.vm.$nextTick();
+ mock = new MockAdapter(axios);
+ mock.onGet(SAST_COMPARISON_PATH).replyOnce(200, sastDiffSuccessMock);
+ mock.onGet(SECRET_SCANNING_COMPARISON_PATH).replyOnce(200, secretScanningDiffSuccessMock);
+ createComponent({
+ propsData: {
+ sastComparisonPath: SAST_COMPARISON_PATH,
+ secretScanningComparisonPath: SECRET_SCANNING_COMPARISON_PATH,
+ },
+ provide: {
+ glFeatures: {
+ coreSecurityMrWidgetCounts: true,
+ coreSecurityMrWidgetDownloads: true,
+ },
+ },
+ apolloProvider: createMockApolloProvider(successHandler),
+ });
+
+ return waitForPromises();
});
- it('calls the pipelineJobs API correctly', () => {
- expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
- expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId, anyParams);
+ afterEach(() => {
+ mock.restore();
});
- it('renders nothing', () => {
- expect(wrapper.html()).toBe('');
+ it('renders the download dropdown', () => {
+ expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
});
- it('calls Flash correctly', () => {
- expect(Flash.mock.calls).toEqual([
- [
- {
- message: SecurityReportsApp.i18n.apiError,
- captureError: true,
- error,
- },
- ],
- ]);
+ it('renders the expected counts message', () => {
+ expect(trimText(wrapper.text())).toContain(
+ 'Security scanning detected 3 potential vulnerabilities 2 Critical 1 High and 0 Others',
+ );
+ });
+
+ it('should not render the pipeline tab anchor', () => {
+ expect(findPipelinesTabAnchor().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/vue_shared/security_reports/store/getters_spec.js b/spec/frontend/vue_shared/security_reports/store/getters_spec.js
new file mode 100644
index 00000000000..8de704be455
--- /dev/null
+++ b/spec/frontend/vue_shared/security_reports/store/getters_spec.js
@@ -0,0 +1,182 @@
+import createState from '~/vue_shared/security_reports/store/state';
+import createSastState from '~/vue_shared/security_reports/store/modules/sast/state';
+import createSecretScanningState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
+import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils';
+import {
+ groupedSummaryText,
+ allReportsHaveError,
+ areReportsLoading,
+ anyReportHasError,
+ areAllReportsLoading,
+ anyReportHasIssues,
+ summaryCounts,
+} from '~/vue_shared/security_reports/store/getters';
+import { CRITICAL, HIGH, LOW } from '~/vulnerabilities/constants';
+
+const generateVuln = severity => ({ severity });
+
+describe('Security reports getters', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ state.sast = createSastState();
+ state.secretDetection = createSecretScanningState();
+ });
+
+ describe('summaryCounts', () => {
+ it('returns 0 count for empty state', () => {
+ expect(summaryCounts(state)).toEqual({
+ critical: 0,
+ high: 0,
+ other: 0,
+ });
+ });
+
+ describe('combines all reports', () => {
+ it('of the same severity', () => {
+ state.sast.newIssues = [generateVuln(CRITICAL)];
+ state.secretDetection.newIssues = [generateVuln(CRITICAL)];
+
+ expect(summaryCounts(state)).toEqual({
+ critical: 2,
+ high: 0,
+ other: 0,
+ });
+ });
+
+ it('of different severities', () => {
+ state.sast.newIssues = [generateVuln(CRITICAL)];
+ state.secretDetection.newIssues = [generateVuln(HIGH), generateVuln(LOW)];
+
+ expect(summaryCounts(state)).toEqual({
+ critical: 1,
+ high: 1,
+ other: 1,
+ });
+ });
+ });
+ });
+
+ describe('groupedSummaryText', () => {
+ it('returns failed text', () => {
+ expect(
+ groupedSummaryText(state, {
+ allReportsHaveError: true,
+ areReportsLoading: false,
+ summaryCounts: {},
+ }),
+ ).toEqual({ message: 'Security scanning failed loading any results' });
+ });
+
+ it('returns `is loading` as status text', () => {
+ expect(
+ groupedSummaryText(state, {
+ allReportsHaveError: false,
+ areReportsLoading: true,
+ summaryCounts: {},
+ }),
+ ).toEqual(
+ groupedTextBuilder({
+ reportType: 'Security scanning',
+ critical: 0,
+ high: 0,
+ other: 0,
+ status: 'is loading',
+ }),
+ );
+ });
+
+ it('returns no new status text if there are existing ones', () => {
+ expect(
+ groupedSummaryText(state, {
+ allReportsHaveError: false,
+ areReportsLoading: false,
+ summaryCounts: {},
+ }),
+ ).toEqual(
+ groupedTextBuilder({
+ reportType: 'Security scanning',
+ critical: 0,
+ high: 0,
+ other: 0,
+ status: '',
+ }),
+ );
+ });
+ });
+
+ describe('areReportsLoading', () => {
+ it('returns true when any report is loading', () => {
+ state.sast.isLoading = true;
+
+ expect(areReportsLoading(state)).toEqual(true);
+ });
+
+ it('returns false when none of the reports are loading', () => {
+ expect(areReportsLoading(state)).toEqual(false);
+ });
+ });
+
+ describe('areAllReportsLoading', () => {
+ it('returns true when all reports are loading', () => {
+ state.sast.isLoading = true;
+ state.secretDetection.isLoading = true;
+
+ expect(areAllReportsLoading(state)).toEqual(true);
+ });
+
+ it('returns false when some of the reports are loading', () => {
+ state.sast.isLoading = true;
+
+ expect(areAllReportsLoading(state)).toEqual(false);
+ });
+
+ it('returns false when none of the reports are loading', () => {
+ expect(areAllReportsLoading(state)).toEqual(false);
+ });
+ });
+
+ describe('allReportsHaveError', () => {
+ it('returns true when all reports have error', () => {
+ state.sast.hasError = true;
+ state.secretDetection.hasError = true;
+
+ expect(allReportsHaveError(state)).toEqual(true);
+ });
+
+ it('returns false when none of the reports have error', () => {
+ expect(allReportsHaveError(state)).toEqual(false);
+ });
+
+ it('returns false when one of the reports does not have error', () => {
+ state.secretDetection.hasError = true;
+
+ expect(allReportsHaveError(state)).toEqual(false);
+ });
+ });
+
+ describe('anyReportHasError', () => {
+ it('returns true when any of the reports has error', () => {
+ state.sast.hasError = true;
+
+ expect(anyReportHasError(state)).toEqual(true);
+ });
+
+ it('returns false when none of the reports has error', () => {
+ expect(anyReportHasError(state)).toEqual(false);
+ });
+ });
+
+ describe('anyReportHasIssues', () => {
+ it('returns true when any of the reports has new issues', () => {
+ state.sast.newIssues.push(generateVuln(LOW));
+
+ expect(anyReportHasIssues(state)).toEqual(true);
+ });
+
+ it('returns false when none of the reports has error', () => {
+ expect(anyReportHasIssues(state)).toEqual(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/security_reports/utils_spec.js b/spec/frontend/vue_shared/security_reports/utils_spec.js
new file mode 100644
index 00000000000..ea54644796a
--- /dev/null
+++ b/spec/frontend/vue_shared/security_reports/utils_spec.js
@@ -0,0 +1,28 @@
+import { extractSecurityReportArtifacts } from '~/vue_shared/security_reports/utils';
+import {
+ REPORT_TYPE_SAST,
+ REPORT_TYPE_SECRET_DETECTION,
+} from '~/vue_shared/security_reports/constants';
+import {
+ securityReportDownloadPathsQueryResponse,
+ sastArtifacts,
+ secretDetectionArtifacts,
+} from './mock_data';
+
+describe('extractSecurityReportArtifacts', () => {
+ it.each`
+ reportTypes | expectedArtifacts
+ ${[]} | ${[]}
+ ${['foo']} | ${[]}
+ ${[REPORT_TYPE_SAST]} | ${sastArtifacts}
+ ${[REPORT_TYPE_SECRET_DETECTION]} | ${secretDetectionArtifacts}
+ ${[REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION]} | ${[...secretDetectionArtifacts, ...sastArtifacts]}
+ `(
+ 'returns the expected artifacts given report types $reportTypes',
+ ({ reportTypes, expectedArtifacts }) => {
+ expect(
+ extractSecurityReportArtifacts(reportTypes, securityReportDownloadPathsQueryResponse),
+ ).toEqual(expectedArtifacts);
+ },
+ );
+});
diff --git a/spec/frontend/whats_new/components/app_spec.js b/spec/frontend/whats_new/components/app_spec.js
index cba550b19db..7a9340da87a 100644
--- a/spec/frontend/whats_new/components/app_spec.js
+++ b/spec/frontend/whats_new/components/app_spec.js
@@ -1,6 +1,6 @@
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlDrawer, GlInfiniteScroll } from '@gitlab/ui';
+import { GlDrawer, GlInfiniteScroll, GlTabs } from '@gitlab/ui';
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import App from '~/whats_new/components/app.vue';
@@ -16,12 +16,18 @@ const localVue = createLocalVue();
localVue.use(Vuex);
describe('App', () => {
- const propsData = { storageKey: 'storage-key' };
let wrapper;
let store;
let actions;
let state;
let trackingSpy;
+ let gitlabDotCom = true;
+
+ const buildProps = () => ({
+ storageKey: 'storage-key',
+ versions: ['3.11', '3.10'],
+ gitlabDotCom,
+ });
const buildWrapper = () => {
actions = {
@@ -45,7 +51,7 @@ describe('App', () => {
wrapper = mount(App, {
localVue,
store,
- propsData,
+ propsData: buildProps(),
directives: {
GlResizeObserver: createMockDirective(),
},
@@ -53,112 +59,171 @@ describe('App', () => {
};
const findInfiniteScroll = () => wrapper.find(GlInfiniteScroll);
- const emitBottomReached = () => findInfiniteScroll().vm.$emit('bottomReached');
- beforeEach(async () => {
+ const setup = async () => {
document.body.dataset.page = 'test-page';
document.body.dataset.namespaceId = 'namespace-840';
trackingSpy = mockTracking('_category_', null, jest.spyOn);
buildWrapper();
- wrapper.vm.$store.state.features = [{ title: 'Whats New Drawer', url: 'www.url.com' }];
+ wrapper.vm.$store.state.features = [
+ { title: 'Whats New Drawer', url: 'www.url.com', release: 3.11 },
+ ];
wrapper.vm.$store.state.drawerBodyHeight = MOCK_DRAWER_BODY_HEIGHT;
await wrapper.vm.$nextTick();
- });
+ };
afterEach(() => {
wrapper.destroy();
unmockTracking();
});
- const getDrawer = () => wrapper.find(GlDrawer);
+ describe('gitlab.com', () => {
+ beforeEach(() => {
+ setup();
+ });
- it('contains a drawer', () => {
- expect(getDrawer().exists()).toBe(true);
- });
+ const getDrawer = () => wrapper.find(GlDrawer);
- it('dispatches openDrawer and tracking calls when mounted', () => {
- expect(actions.openDrawer).toHaveBeenCalledWith(expect.any(Object), 'storage-key');
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_whats_new_drawer', {
- label: 'namespace_id',
- value: 'namespace-840',
+ it('contains a drawer', () => {
+ expect(getDrawer().exists()).toBe(true);
});
- });
- it('dispatches closeDrawer when clicking close', () => {
- getDrawer().vm.$emit('close');
- expect(actions.closeDrawer).toHaveBeenCalled();
- });
+ it('dispatches openDrawer and tracking calls when mounted', () => {
+ expect(actions.openDrawer).toHaveBeenCalledWith(expect.any(Object), 'storage-key');
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_whats_new_drawer', {
+ label: 'namespace_id',
+ value: 'namespace-840',
+ });
+ });
- it.each([true, false])('passes open property', async openState => {
- wrapper.vm.$store.state.open = openState;
+ it('dispatches closeDrawer when clicking close', () => {
+ getDrawer().vm.$emit('close');
+ expect(actions.closeDrawer).toHaveBeenCalled();
+ });
- await wrapper.vm.$nextTick();
+ it.each([true, false])('passes open property', async openState => {
+ wrapper.vm.$store.state.open = openState;
- expect(getDrawer().props('open')).toBe(openState);
- });
+ await wrapper.vm.$nextTick();
- it('renders features when provided via ajax', () => {
- expect(actions.fetchItems).toHaveBeenCalled();
- expect(wrapper.find('h5').text()).toBe('Whats New Drawer');
- });
+ expect(getDrawer().props('open')).toBe(openState);
+ });
- it('send an event when feature item is clicked', () => {
- trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
+ it('renders features when provided via ajax', () => {
+ expect(actions.fetchItems).toHaveBeenCalled();
+ expect(wrapper.find('[data-test-id="feature-title"]').text()).toBe('Whats New Drawer');
+ });
- const link = wrapper.find('.whats-new-item-title-link');
- triggerEvent(link.element);
+ it('send an event when feature item is clicked', () => {
+ trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
- expect(trackingSpy.mock.calls[1]).toMatchObject([
- '_category_',
- 'click_whats_new_item',
- {
- label: 'Whats New Drawer',
- property: 'www.url.com',
- },
- ]);
- });
+ const link = wrapper.find('.whats-new-item-title-link');
+ triggerEvent(link.element);
+
+ expect(trackingSpy.mock.calls[1]).toMatchObject([
+ '_category_',
+ 'click_whats_new_item',
+ {
+ label: 'Whats New Drawer',
+ property: 'www.url.com',
+ },
+ ]);
+ });
+
+ it('renders infinite scroll', () => {
+ const scroll = findInfiniteScroll();
+
+ expect(scroll.props()).toMatchObject({
+ fetchedItems: wrapper.vm.$store.state.features.length,
+ maxListHeight: MOCK_DRAWER_BODY_HEIGHT,
+ });
+ });
+
+ describe('bottomReached', () => {
+ const emitBottomReached = () => findInfiniteScroll().vm.$emit('bottomReached');
- it('renders infinite scroll', () => {
- const scroll = findInfiniteScroll();
+ beforeEach(() => {
+ actions.fetchItems.mockClear();
+ });
- expect(scroll.props()).toMatchObject({
- fetchedItems: wrapper.vm.$store.state.features.length,
- maxListHeight: MOCK_DRAWER_BODY_HEIGHT,
+ it('when nextPage exists it calls fetchItems', () => {
+ wrapper.vm.$store.state.pageInfo = { nextPage: 840 };
+ emitBottomReached();
+
+ expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), { page: 840 });
+ });
+
+ it('when nextPage does not exist it does not call fetchItems', () => {
+ wrapper.vm.$store.state.pageInfo = { nextPage: null };
+ emitBottomReached();
+
+ expect(actions.fetchItems).not.toHaveBeenCalled();
+ });
+ });
+
+ it('calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered', () => {
+ const { value } = getBinding(getDrawer().element, 'gl-resize-observer');
+
+ value();
+
+ expect(getDrawerBodyHeight).toHaveBeenCalledWith(wrapper.find(GlDrawer).element);
+
+ expect(actions.setDrawerBodyHeight).toHaveBeenCalledWith(
+ expect.any(Object),
+ MOCK_DRAWER_BODY_HEIGHT,
+ );
});
});
- describe('bottomReached', () => {
+ describe('self managed', () => {
+ const findTabs = () => wrapper.find(GlTabs);
+
+ const clickSecondTab = async () => {
+ const secondTab = wrapper.findAll('.nav-link').at(1);
+ await secondTab.trigger('click');
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ };
+
beforeEach(() => {
- actions.fetchItems.mockClear();
+ gitlabDotCom = false;
+ setup();
});
- it('when nextPage exists it calls fetchItems', () => {
- wrapper.vm.$store.state.pageInfo = { nextPage: 840 };
- emitBottomReached();
+ it('renders tabs with drawer body height and content', () => {
+ const scroll = findInfiniteScroll();
+ const tabs = findTabs();
- expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), 840);
+ expect(scroll.exists()).toBe(false);
+ expect(tabs.attributes().style).toBe(`height: ${MOCK_DRAWER_BODY_HEIGHT}px;`);
+ expect(wrapper.find('h5').text()).toBe('Whats New Drawer');
});
- it('when nextPage does not exist it does not call fetchItems', () => {
- wrapper.vm.$store.state.pageInfo = { nextPage: null };
- emitBottomReached();
+ describe('fetchVersion', () => {
+ beforeEach(() => {
+ actions.fetchItems.mockClear();
+ });
- expect(actions.fetchItems).not.toHaveBeenCalled();
- });
- });
+ it('when version isnt fetched, clicking a tab calls fetchItems', async () => {
+ const fetchVersionSpy = jest.spyOn(wrapper.vm, 'fetchVersion');
+ await clickSecondTab();
- it('calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered', () => {
- const { value } = getBinding(getDrawer().element, 'gl-resize-observer');
+ expect(fetchVersionSpy).toHaveBeenCalledWith('3.10');
+ expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), { version: '3.10' });
+ });
- value();
+ it('when version has been fetched, clicking a tab calls fetchItems', async () => {
+ wrapper.vm.$store.state.features.push({ title: 'GitLab Stories', release: 3.1 });
+ await wrapper.vm.$nextTick();
- expect(getDrawerBodyHeight).toHaveBeenCalledWith(wrapper.find(GlDrawer).element);
+ const fetchVersionSpy = jest.spyOn(wrapper.vm, 'fetchVersion');
+ await clickSecondTab();
- expect(actions.setDrawerBodyHeight).toHaveBeenCalledWith(
- expect.any(Object),
- MOCK_DRAWER_BODY_HEIGHT,
- );
+ expect(fetchVersionSpy).toHaveBeenCalledWith('3.10');
+ expect(actions.fetchItems).not.toHaveBeenCalled();
+ expect(wrapper.find('.tab-pane.active h5').text()).toBe('GitLab Stories');
+ });
+ });
});
});
diff --git a/spec/frontend/whats_new/store/actions_spec.js b/spec/frontend/whats_new/store/actions_spec.js
index 12722b1b3b1..82f17a2726f 100644
--- a/spec/frontend/whats_new/store/actions_spec.js
+++ b/spec/frontend/whats_new/store/actions_spec.js
@@ -41,6 +41,23 @@ describe('whats new actions', () => {
axiosMock.restore();
});
+ it('passes arguments', () => {
+ axiosMock.reset();
+
+ axiosMock
+ .onGet('/-/whats_new', { params: { page: 8, version: 40 } })
+ .replyOnce(200, [{ title: 'GitLab Stories' }]);
+
+ testAction(
+ actions.fetchItems,
+ { page: 8, version: 40 },
+ {},
+ expect.arrayContaining([
+ { type: types.ADD_FEATURES, payload: [{ title: 'GitLab Stories' }] },
+ ]),
+ );
+ });
+
it('if already fetching, does not fetch', () => {
testAction(actions.fetchItems, {}, { fetching: true }, []);
});
diff --git a/spec/frontend/whats_new/utils/notification_spec.js b/spec/frontend/whats_new/utils/notification_spec.js
new file mode 100644
index 00000000000..e3e390f4394
--- /dev/null
+++ b/spec/frontend/whats_new/utils/notification_spec.js
@@ -0,0 +1,55 @@
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import { setNotification, getStorageKey } from '~/whats_new/utils/notification';
+
+describe('~/whats_new/utils/notification', () => {
+ useLocalStorageSpy();
+
+ let wrapper;
+
+ const findNotificationEl = () => wrapper.querySelector('.header-help');
+ const findNotificationCountEl = () => wrapper.querySelector('.js-whats-new-notification-count');
+ const getAppEl = () => wrapper.querySelector('.app');
+
+ beforeEach(() => {
+ loadFixtures('static/whats_new_notification.html');
+ wrapper = document.querySelector('.whats-new-notification-fixture-root');
+ });
+
+ afterEach(() => {
+ wrapper.remove();
+ });
+
+ describe('setNotification', () => {
+ const subject = () => setNotification(getAppEl());
+
+ it("when storage key doesn't exist it adds notifications class", () => {
+ const notificationEl = findNotificationEl();
+
+ expect(notificationEl.classList).not.toContain('with-notifications');
+
+ subject();
+
+ expect(findNotificationCountEl()).toExist();
+ expect(notificationEl.classList).toContain('with-notifications');
+ });
+
+ it('removes class and count element when storage key is true', () => {
+ const notificationEl = findNotificationEl();
+ notificationEl.classList.add('with-notifications');
+ localStorage.setItem('storage-key', 'false');
+
+ expect(findNotificationCountEl()).toExist();
+
+ subject();
+
+ expect(findNotificationCountEl()).not.toExist();
+ expect(notificationEl.classList).not.toContain('with-notifications');
+ });
+ });
+
+ describe('getStorageKey', () => {
+ it('retrieves the storage key data attribute from the el', () => {
+ expect(getStorageKey(getAppEl())).toBe('storage-key');
+ });
+ });
+});
diff --git a/spec/frontend_integration/.eslintrc.yml b/spec/frontend_integration/.eslintrc.yml
index 2460e218f59..8fff491bdcf 100644
--- a/spec/frontend_integration/.eslintrc.yml
+++ b/spec/frontend_integration/.eslintrc.yml
@@ -4,5 +4,9 @@ settings:
import/resolver:
jest:
jestConfigFile: 'jest.config.integration.js'
+rules:
+ no-restricted-imports:
+ - error
+ - fs
globals:
mockServer: false
diff --git a/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap b/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap
deleted file mode 100644
index 877cc78a111..00000000000
--- a/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap
+++ /dev/null
@@ -1,114 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`WebIDE runs 1`] = `
-<div>
- <article
- class="ide position-relative d-flex flex-column align-items-stretch"
- >
- <div
- class="ide-view flex-grow d-flex"
- >
- <div
- class="gl-relative multi-file-commit-panel flex-column"
- style="width: 340px;"
- >
- <div
- class="multi-file-commit-panel-inner"
- data-testid="ide-side-bar-inner"
- >
- <div
- class="multi-file-loading-container"
- >
- <div
- class="animation-container"
- >
- <div
- class="skeleton-line-1"
- />
- <div
- class="skeleton-line-2"
- />
- <div
- class="skeleton-line-3"
- />
- </div>
- </div>
- <div
- class="multi-file-loading-container"
- >
- <div
- class="animation-container"
- >
- <div
- class="skeleton-line-1"
- />
- <div
- class="skeleton-line-2"
- />
- <div
- class="skeleton-line-3"
- />
- </div>
- </div>
- <div
- class="multi-file-loading-container"
- >
- <div
- class="animation-container"
- >
- <div
- class="skeleton-line-1"
- />
- <div
- class="skeleton-line-2"
- />
- <div
- class="skeleton-line-3"
- />
- </div>
- </div>
- </div>
- <div
- class="position-absolute position-top-0 position-bottom-0 drag-handle position-right-0"
- size="340"
- style="cursor: ew-resize;"
- />
- </div>
- <div
- class="multi-file-edit-pane"
- >
- <div
- class="ide-empty-state"
- >
- <div
- class="row js-empty-state"
- >
- <div
- class="col-12"
- >
- <div
- class="svg-content svg-250"
- >
- <img
- src="/test/empty_state.svg"
- />
- </div>
- </div>
- <div
- class="col-12"
- >
- <div
- class="text-content text-center"
- >
- <h4>
- Make and review changes in the browser with the Web IDE
- </h4>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </article>
-</div>
-`;
diff --git a/spec/frontend_integration/ide/ide_helper.js b/spec/frontend_integration/ide/helpers/ide_helper.js
index fea8bc24031..fe8d5f93794 100644
--- a/spec/frontend_integration/ide/ide_helper.js
+++ b/spec/frontend_integration/ide/helpers/ide_helper.js
@@ -1,10 +1,18 @@
-import { findAllByText, fireEvent, getByLabelText, screen } from '@testing-library/dom';
+import {
+ findAllByText,
+ fireEvent,
+ getByLabelText,
+ findByTestId,
+ getByText,
+ screen,
+ findByText,
+} from '@testing-library/dom';
const isFolderRowOpen = row => row.matches('.folder.is-open');
const getLeftSidebar = () => screen.getByTestId('left-sidebar');
-const clickOnLeftSidebarTab = name => {
+export const switchLeftSidebarTab = name => {
const sidebar = getLeftSidebar();
const button = getByLabelText(sidebar, name);
@@ -12,16 +20,31 @@ const clickOnLeftSidebarTab = name => {
button.click();
};
-const findMonacoEditor = () =>
- screen.findByLabelText(/Editor content;/).then(x => x.closest('.monaco-editor'));
+export const getStatusBar = () => document.querySelector('.ide-status-bar');
-const findAndSetEditorValue = async value => {
+export const waitForMonacoEditor = () =>
+ new Promise(resolve => window.monaco.editor.onDidCreateEditor(resolve));
+
+export const findMonacoEditor = () =>
+ screen.findAllByLabelText(/Editor content;/).then(([x]) => x.closest('.monaco-editor'));
+
+export const findMonacoDiffEditor = () =>
+ screen.findAllByLabelText(/Editor content;/).then(([x]) => x.closest('.monaco-diff-editor'));
+
+export const findAndSetEditorValue = async value => {
const editor = await findMonacoEditor();
const uri = editor.getAttribute('data-uri');
window.monaco.editor.getModel(uri).setValue(value);
};
+export const getEditorValue = async () => {
+ const editor = await findMonacoEditor();
+ const uri = editor.getAttribute('data-uri');
+
+ return window.monaco.editor.getModel(uri).getValue();
+};
+
const findTreeBody = () => screen.findByTestId('ide-tree-body', {}, { timeout: 5000 });
const findRootActions = () => screen.findByTestId('ide-root-actions', {}, { timeout: 7000 });
@@ -68,11 +91,13 @@ const clickFileRowAction = (row, name) => {
dropdownAction.click();
};
-const findAndSetFileName = async value => {
- const nameField = await screen.findByTestId('file-name-field');
+const fillFileNameModal = async (value, submitText = 'Create file') => {
+ const modal = await screen.findByTestId('ide-new-entry');
+
+ const nameField = await findByTestId(modal, 'file-name-field');
fireEvent.input(nameField, { target: { value } });
- const createButton = screen.getByText('Create file');
+ const createButton = getByText(modal, submitText, { selector: 'button' });
createButton.click();
};
@@ -83,12 +108,19 @@ const findAndClickRootAction = async name => {
button.click();
};
+export const clickPreviewMarkdown = () => {
+ screen.getByText('Preview Markdown').click();
+};
+
export const openFile = async path => {
const row = await findAndTraverseToPath(path);
openFileRow(row);
};
+export const waitForTabToOpen = fileName =>
+ findByText(document.querySelector('.multi-file-edit-pane'), fileName);
+
export const createFile = async (path, content) => {
const parentPath = path
.split('/')
@@ -103,17 +135,36 @@ export const createFile = async (path, content) => {
await findAndClickRootAction('New file');
}
- await findAndSetFileName(path);
+ await fillFileNameModal(path);
await findAndSetEditorValue(content);
};
+export const getFilesList = () => {
+ return screen.getAllByTestId('file-row-name-container').map(e => e.textContent.trim());
+};
+
export const deleteFile = async path => {
const row = await findAndTraverseToPath(path);
clickFileRowAction(row, 'Delete');
};
+export const renameFile = async (path, newPath) => {
+ const row = await findAndTraverseToPath(path);
+ clickFileRowAction(row, 'Rename/Move');
+
+ await fillFileNameModal(newPath, 'Rename file');
+};
+
+export const closeFile = async path => {
+ const button = await screen.getByLabelText(`Close ${path}`, {
+ selector: '.multi-file-tabs button',
+ });
+
+ button.click();
+};
+
export const commit = async () => {
- clickOnLeftSidebarTab('Commit');
+ switchLeftSidebarTab('Commit');
screen.getByTestId('begin-commit-button').click();
await screen.findByLabelText(/Commit to .+ branch/).then(x => x.click());
diff --git a/spec/frontend_integration/ide/helpers/mock_data.js b/spec/frontend_integration/ide/helpers/mock_data.js
new file mode 100644
index 00000000000..f70739e5ac0
--- /dev/null
+++ b/spec/frontend_integration/ide/helpers/mock_data.js
@@ -0,0 +1,12 @@
+export const IDE_DATASET = {
+ emptyStateSvgPath: '/test/empty_state.svg',
+ noChangesStateSvgPath: '/test/no_changes_state.svg',
+ committedStateSvgPath: '/test/committed_state.svg',
+ pipelinesEmptyStateSvgPath: '/test/pipelines_empty_state.svg',
+ promotionSvgPath: '/test/promotion.svg',
+ ciHelpPagePath: '/test/ci_help_page',
+ webIDEHelpPagePath: '/test/web_ide_help_page',
+ clientsidePreviewEnabled: 'true',
+ renderWhitespaceInCode: 'false',
+ codesandboxBundlerUrl: 'test/codesandbox_bundler',
+};
diff --git a/spec/frontend_integration/ide/helpers/start.js b/spec/frontend_integration/ide/helpers/start.js
new file mode 100644
index 00000000000..9dc9649e1bf
--- /dev/null
+++ b/spec/frontend_integration/ide/helpers/start.js
@@ -0,0 +1,17 @@
+import { TEST_HOST } from 'helpers/test_constants';
+import extendStore from '~/ide/stores/extend';
+import { IDE_DATASET } from './mock_data';
+import { initIde } from '~/ide';
+
+export default (container, { isRepoEmpty = false, path = '' } = {}) => {
+ global.jsdom.reconfigure({
+ url: `${TEST_HOST}/-/ide/project/gitlab-test/lorem-ipsum${
+ isRepoEmpty ? '-empty' : ''
+ }/tree/master/-/${path}`,
+ });
+
+ const el = document.createElement('div');
+ Object.assign(el.dataset, IDE_DATASET);
+ container.appendChild(el);
+ return initIde(el, { extendStore });
+};
diff --git a/spec/frontend_integration/ide/ide_integration_spec.js b/spec/frontend_integration/ide/ide_integration_spec.js
index 1f5c1d38450..dacc538d5ba 100644
--- a/spec/frontend_integration/ide/ide_integration_spec.js
+++ b/spec/frontend_integration/ide/ide_integration_spec.js
@@ -1,61 +1,28 @@
-import { TEST_HOST } from 'helpers/test_constants';
import { waitForText } from 'helpers/wait_for_text';
import waitForPromises from 'helpers/wait_for_promises';
import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
import { createCommitId } from 'test_helpers/factories/commit_id';
-import { initIde } from '~/ide';
-import extendStore from '~/ide/stores/extend';
-import * as ideHelper from './ide_helper';
-
-const TEST_DATASET = {
- emptyStateSvgPath: '/test/empty_state.svg',
- noChangesStateSvgPath: '/test/no_changes_state.svg',
- committedStateSvgPath: '/test/committed_state.svg',
- pipelinesEmptyStateSvgPath: '/test/pipelines_empty_state.svg',
- promotionSvgPath: '/test/promotion.svg',
- ciHelpPagePath: '/test/ci_help_page',
- webIDEHelpPagePath: '/test/web_ide_help_page',
- clientsidePreviewEnabled: 'true',
- renderWhitespaceInCode: 'false',
- codesandboxBundlerUrl: 'test/codesandbox_bundler',
-};
+import * as ideHelper from './helpers/ide_helper';
+import startWebIDE from './helpers/start';
describe('WebIDE', () => {
useOverclockTimers();
let vm;
- let root;
+ let container;
beforeEach(() => {
- root = document.createElement('div');
- document.body.appendChild(root);
-
- global.jsdom.reconfigure({
- url: `${TEST_HOST}/-/ide/project/gitlab-test/lorem-ipsum`,
- });
+ setFixtures('<div class="webide-container"></div>');
+ container = document.querySelector('.webide-container');
});
afterEach(() => {
vm.$destroy();
vm = null;
- root.remove();
- });
-
- const createComponent = () => {
- const el = document.createElement('div');
- Object.assign(el.dataset, TEST_DATASET);
- root.appendChild(el);
- vm = initIde(el, { extendStore });
- };
-
- it('runs', () => {
- createComponent();
-
- expect(root).toMatchSnapshot();
});
it('user commits changes', async () => {
- createComponent();
+ vm = startWebIDE(container);
await ideHelper.createFile('foo/bar/test.txt', 'Lorem ipsum dolar sit');
await ideHelper.deleteFile('foo/bar/.gitkeep');
@@ -89,7 +56,7 @@ describe('WebIDE', () => {
});
it('user adds file that starts with +', async () => {
- createComponent();
+ vm = startWebIDE(container);
await ideHelper.createFile('+test', 'Hello world!');
await ideHelper.openFile('+test');
@@ -101,4 +68,93 @@ describe('WebIDE', () => {
const tabs = Array.from(document.querySelectorAll('.multi-file-tab'));
expect(tabs.map(x => x.textContent.trim())).toEqual(['+test']);
});
+
+ describe('editor info', () => {
+ let statusBar;
+ let editor;
+
+ const waitForEditor = async () => {
+ editor = await ideHelper.waitForMonacoEditor();
+ };
+
+ const changeEditorPosition = async (lineNumber, column) => {
+ editor.setPosition({ lineNumber, column });
+
+ await vm.$nextTick();
+ };
+
+ beforeEach(async () => {
+ vm = startWebIDE(container);
+
+ await ideHelper.openFile('README.md');
+ editor = await ideHelper.waitForMonacoEditor();
+
+ statusBar = ideHelper.getStatusBar();
+ });
+
+ it('shows line position and type', () => {
+ expect(statusBar).toHaveText('1:1');
+ expect(statusBar).toHaveText('markdown');
+ });
+
+ it('persists viewer', async () => {
+ const markdownPreview = 'test preview_markdown result';
+ mockServer.post('/:namespace/:project/preview_markdown', () => ({
+ body: markdownPreview,
+ }));
+
+ await ideHelper.openFile('README.md');
+ ideHelper.clickPreviewMarkdown();
+
+ const el = await waitForText(markdownPreview);
+ expect(el).toHaveText(markdownPreview);
+
+ // Need to wait for monaco editor to load so it doesn't through errors on dispose
+ await ideHelper.openFile('.gitignore');
+ await ideHelper.waitForMonacoEditor();
+ await ideHelper.openFile('README.md');
+ await ideHelper.waitForMonacoEditor();
+
+ expect(el).toHaveText(markdownPreview);
+ });
+
+ describe('when editor position changes', () => {
+ beforeEach(async () => {
+ await changeEditorPosition(4, 10);
+ });
+
+ it('shows new line position', () => {
+ expect(statusBar).not.toHaveText('1:1');
+ expect(statusBar).toHaveText('4:10');
+ });
+
+ it('updates after rename', async () => {
+ await ideHelper.renameFile('README.md', 'READMEZ.txt');
+ await waitForEditor();
+
+ expect(statusBar).toHaveText('1:1');
+ expect(statusBar).toHaveText('plaintext');
+ });
+
+ it('persists position after opening then rename', async () => {
+ await ideHelper.openFile('files/js/application.js');
+ await waitForEditor();
+ await ideHelper.renameFile('README.md', 'READING_RAINBOW.md');
+ await ideHelper.openFile('READING_RAINBOW.md');
+ await waitForEditor();
+
+ expect(statusBar).toHaveText('4:10');
+ expect(statusBar).toHaveText('markdown');
+ });
+
+ it('persists position after closing', async () => {
+ await ideHelper.closeFile('README.md');
+ await ideHelper.openFile('README.md');
+ await waitForEditor();
+
+ expect(statusBar).toHaveText('4:10');
+ expect(statusBar).toHaveText('markdown');
+ });
+ });
+ });
});
diff --git a/spec/frontend_integration/ide/user_opens_file_spec.js b/spec/frontend_integration/ide/user_opens_file_spec.js
new file mode 100644
index 00000000000..98a73c7a029
--- /dev/null
+++ b/spec/frontend_integration/ide/user_opens_file_spec.js
@@ -0,0 +1,89 @@
+import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
+import { screen } from '@testing-library/dom';
+import * as ideHelper from './helpers/ide_helper';
+import startWebIDE from './helpers/start';
+
+// https://gitlab.com/gitlab-org/gitlab/-/issues/293654#note_466432769
+// eslint-disable-next-line jest/no-disabled-tests
+describe.skip('IDE: User opens a file in the Web IDE', () => {
+ useOverclockTimers();
+
+ let vm;
+ let container;
+
+ beforeEach(async () => {
+ setFixtures('<div class="webide-container"></div>');
+ container = document.querySelector('.webide-container');
+
+ vm = startWebIDE(container);
+
+ await screen.findByText('README'); // wait for file tree to load
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ vm = null;
+ });
+
+ describe('user opens a directory', () => {
+ beforeEach(async () => {
+ await ideHelper.openFile('files/images');
+ await screen.findByText('logo-white.png');
+ });
+
+ it('expands directory in the left sidebar', () => {
+ expect(ideHelper.getFilesList()).toEqual(
+ expect.arrayContaining(['html', 'js', 'images', 'logo-white.png']),
+ );
+ });
+ });
+
+ describe('user opens a text file', () => {
+ beforeEach(async () => {
+ await ideHelper.openFile('README.md');
+ await ideHelper.waitForTabToOpen('README.md');
+ });
+
+ it('opens the file in monaco editor', async () => {
+ expect(await ideHelper.getEditorValue()).toContain('Sample repo for testing gitlab features');
+ });
+
+ describe('user switches to review mode', () => {
+ beforeEach(() => {
+ ideHelper.switchLeftSidebarTab('Review');
+ });
+
+ it('shows diff editor', async () => {
+ expect(await ideHelper.findMonacoDiffEditor()).toBeDefined();
+ });
+ });
+ });
+
+ describe('user opens an image file', () => {
+ beforeEach(async () => {
+ await ideHelper.openFile('files/images/logo-white.png');
+ await ideHelper.waitForTabToOpen('logo-white.png');
+ });
+
+ it('opens image viewer for the file', async () => {
+ const viewer = await screen.findByTestId('image-viewer');
+ const img = viewer.querySelector('img');
+
+ expect(img.src).toContain('logo-white.png');
+ });
+ });
+
+ describe('user opens a binary file', () => {
+ beforeEach(async () => {
+ await ideHelper.openFile('Gemfile.zip');
+ await ideHelper.waitForTabToOpen('Gemfile.zip');
+ });
+
+ it('opens image viewer for the file', async () => {
+ const downloadButton = await screen.findByText('Download');
+
+ expect(downloadButton.getAttribute('download')).toEqual('Gemfile.zip');
+ expect(downloadButton.getAttribute('href')).toContain('/raw/');
+ });
+ });
+});
diff --git a/spec/frontend_integration/ide/user_opens_ide_spec.js b/spec/frontend_integration/ide/user_opens_ide_spec.js
new file mode 100644
index 00000000000..502cb2e2c7d
--- /dev/null
+++ b/spec/frontend_integration/ide/user_opens_ide_spec.js
@@ -0,0 +1,160 @@
+import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
+import { screen } from '@testing-library/dom';
+import * as ideHelper from './helpers/ide_helper';
+import startWebIDE from './helpers/start';
+
+describe('IDE: User opens IDE', () => {
+ useOverclockTimers();
+
+ let vm;
+ let container;
+
+ beforeEach(() => {
+ setFixtures('<div class="webide-container"></div>');
+ container = document.querySelector('.webide-container');
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ vm = null;
+ });
+
+ it('shows loading indicator while the IDE is loading', async () => {
+ vm = startWebIDE(container);
+
+ expect(container.querySelectorAll('.multi-file-loading-container')).toHaveLength(3);
+ });
+
+ describe('when the project is empty', () => {
+ beforeEach(() => {
+ vm = startWebIDE(container, { isRepoEmpty: true });
+ });
+
+ it('shows "No files" in the left sidebar', async () => {
+ expect(await screen.findByText('No files')).toBeDefined();
+ });
+
+ it('shows a "New file" button', async () => {
+ const button = await screen.findByTitle('New file');
+
+ expect(button.tagName).toEqual('BUTTON');
+ });
+ });
+
+ describe('when the file tree is loaded', () => {
+ beforeEach(async () => {
+ vm = startWebIDE(container);
+
+ await screen.findByText('README'); // wait for file tree to load
+ });
+
+ it('shows a list of files in the left sidebar', async () => {
+ expect(ideHelper.getFilesList()).toEqual(
+ expect.arrayContaining(['README', 'LICENSE', 'CONTRIBUTING.md']),
+ );
+ });
+
+ it('shows empty state in the main editor window', async () => {
+ expect(
+ await screen.findByText(
+ "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes.",
+ ),
+ ).toBeDefined();
+ });
+
+ it('shows commit button in disabled state', async () => {
+ const button = await screen.findByTestId('begin-commit-button');
+
+ expect(button.getAttribute('disabled')).toBeDefined();
+ });
+
+ it('shows branch/MR dropdown with master selected', async () => {
+ const dropdown = await screen.findByTestId('ide-nav-dropdown');
+
+ expect(dropdown.textContent).toContain('master');
+ });
+ });
+
+ describe('a path to a text file is present in the URL', () => {
+ beforeEach(async () => {
+ vm = startWebIDE(container, { path: 'README.md' });
+
+ await ideHelper.waitForTabToOpen('README.md');
+ });
+
+ it('opens the file and its contents are shown in Monaco', async () => {
+ expect(await ideHelper.getEditorValue()).toContain('Sample repo for testing gitlab features');
+ });
+ });
+
+ describe('a path to a binary file is present in the URL', () => {
+ beforeEach(async () => {
+ vm = startWebIDE(container, { path: 'Gemfile.zip' });
+
+ await ideHelper.waitForTabToOpen('Gemfile.zip');
+ });
+
+ it('shows download viewer', async () => {
+ const downloadButton = await screen.findByText('Download');
+
+ expect(downloadButton.getAttribute('download')).toEqual('Gemfile.zip');
+ expect(downloadButton.getAttribute('href')).toContain('/raw/');
+ });
+ });
+
+ describe('a path to an image is present in the URL', () => {
+ beforeEach(async () => {
+ vm = startWebIDE(container, { path: 'files/images/logo-white.png' });
+
+ await ideHelper.waitForTabToOpen('logo-white.png');
+ });
+
+ it('shows image viewer', async () => {
+ const viewer = await screen.findByTestId('image-viewer');
+ const img = viewer.querySelector('img');
+
+ expect(img.src).toContain('logo-white.png');
+ });
+ });
+
+ describe('path in URL is a directory', () => {
+ beforeEach(async () => {
+ vm = startWebIDE(container, { path: 'files/images' });
+
+ // wait for folders in left sidebar to be expanded
+ await screen.findByText('images');
+ });
+
+ it('expands folders in the left sidebar', () => {
+ expect(ideHelper.getFilesList()).toEqual(
+ expect.arrayContaining(['files', 'images', 'logo-white.png', 'logo-black.png']),
+ );
+ });
+
+ it('shows empty state in the main editor window', async () => {
+ expect(
+ await screen.findByText(
+ "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes.",
+ ),
+ ).toBeDefined();
+ });
+ });
+
+ describe("a file for path in url doesn't exist in the repo", () => {
+ beforeEach(async () => {
+ vm = startWebIDE(container, { path: 'abracadabra/hocus-focus.txt' });
+
+ await ideHelper.waitForTabToOpen('hocus-focus.txt');
+ });
+
+ it('create new folders and file in the left sidebar', () => {
+ expect(ideHelper.getFilesList()).toEqual(
+ expect.arrayContaining(['abracadabra', 'hocus-focus.txt']),
+ );
+ });
+
+ it('creates a blank new file', async () => {
+ expect(await ideHelper.getEditorValue()).toEqual('\n');
+ });
+ });
+});
diff --git a/spec/frontend_integration/test_helpers/fixtures.js b/spec/frontend_integration/test_helpers/fixtures.js
index 50fa4859dfa..46946ed71f2 100644
--- a/spec/frontend_integration/test_helpers/fixtures.js
+++ b/spec/frontend_integration/test_helpers/fixtures.js
@@ -1,10 +1,40 @@
/* eslint-disable global-require, import/no-unresolved */
import { memoize } from 'lodash';
-export const getProject = () => require('test_fixtures/api/projects/get.json');
-export const getBranch = () => require('test_fixtures/api/projects/branches/get.json');
-export const getMergeRequests = () => require('test_fixtures/api/merge_requests/get.json');
-export const getRepositoryFiles = () => require('test_fixtures/projects_json/files.json');
-export const getPipelinesEmptyResponse = () =>
- require('test_fixtures/projects_json/pipelines_empty.json');
+const createFactoryWithDefault = (fn, defaultValue) => () => {
+ try {
+ return fn();
+ } catch {
+ return defaultValue;
+ }
+};
+
+const factory = {
+ json: fn => createFactoryWithDefault(fn, { error: 'fixture not found' }),
+ text: fn => createFactoryWithDefault(fn, 'Hello world\nHow are you today?\n'),
+ binary: fn => createFactoryWithDefault(fn, ''),
+};
+
+export const getProject = factory.json(() => require('test_fixtures/api/projects/get.json'));
+export const getEmptyProject = factory.json(() =>
+ require('test_fixtures/api/projects/get_empty.json'),
+);
+export const getBranch = factory.json(() =>
+ require('test_fixtures/api/projects/branches/get.json'),
+);
+export const getMergeRequests = factory.json(() =>
+ require('test_fixtures/api/merge_requests/get.json'),
+);
+export const getRepositoryFiles = factory.json(() =>
+ require('test_fixtures/projects_json/files.json'),
+);
+export const getPipelinesEmptyResponse = factory.json(() =>
+ require('test_fixtures/projects_json/pipelines_empty.json'),
+);
export const getCommit = memoize(() => getBranch().commit);
+
+export const getBlobReadme = factory.text(() => require('test_fixtures/blob/text/README.md'));
+export const getBlobZip = factory.binary(() => require('test_fixtures/blob/binary/Gemfile.zip'));
+export const getBlobImage = factory.binary(() =>
+ require('test_fixtures/blob/images/logo-white.png'),
+);
diff --git a/spec/frontend_integration/test_helpers/mock_server/index.js b/spec/frontend_integration/test_helpers/mock_server/index.js
index b3979d05ea5..6f090565635 100644
--- a/spec/frontend_integration/test_helpers/mock_server/index.js
+++ b/spec/frontend_integration/test_helpers/mock_server/index.js
@@ -1,5 +1,14 @@
import { Server, Model, RestSerializer } from 'miragejs';
-import { getProject, getBranch, getMergeRequests, getRepositoryFiles } from 'test_helpers/fixtures';
+import {
+ getProject,
+ getEmptyProject,
+ getBranch,
+ getMergeRequests,
+ getRepositoryFiles,
+ getBlobReadme,
+ getBlobImage,
+ getBlobZip,
+} from 'test_helpers/fixtures';
import setupRoutes from './routes';
export const createMockServerOptions = () => ({
@@ -18,9 +27,23 @@ export const createMockServerOptions = () => ({
seeds(schema) {
schema.db.loadData({
files: getRepositoryFiles().map(path => ({ path })),
- projects: [getProject()],
+ projects: [getProject(), getEmptyProject()],
branches: [getBranch()],
mergeRequests: getMergeRequests(),
+ filesRaw: [
+ {
+ raw: getBlobReadme(),
+ path: 'README.md',
+ },
+ {
+ raw: getBlobZip(),
+ path: 'Gemfile.zip',
+ },
+ {
+ raw: getBlobImage(),
+ path: 'files/images/logo-white.png',
+ },
+ ],
userPermissions: [
{
createMergeRequestIn: true,
diff --git a/spec/frontend_integration/test_helpers/mock_server/routes/repository.js b/spec/frontend_integration/test_helpers/mock_server/routes/repository.js
index c5e91c9e87e..166c0cc32db 100644
--- a/spec/frontend_integration/test_helpers/mock_server/routes/repository.js
+++ b/spec/frontend_integration/test_helpers/mock_server/routes/repository.js
@@ -19,6 +19,18 @@ export default server => {
return schema.db.files.map(({ path }) => path);
});
+ server.get('/:namespace/:project/-/blob/:sha/*path', (schema, request) => {
+ const { path } = schema.db.files.findBy({ path: request.params.path });
+
+ return { path, rawPath: request.url.replace('/-/blob', '/-/raw') };
+ });
+
+ server.get('/:namespace/:project/-/raw/:sha/*path', (schema, request) => {
+ const { path } = request.params;
+
+ return schema.db.filesRaw.findBy({ path })?.raw || 'Sample content';
+ });
+
server.post('/api/v4/projects/:id/repository/commits', (schema, request) => {
const { branch: branchName, commit_message: message, actions } = JSON.parse(
request.requestBody,
diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb
index e40c44925e2..ec67ed16fe9 100644
--- a/spec/graphql/features/authorization_spec.rb
+++ b/spec/graphql/features/authorization_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe 'with a single permission' do
let(:query_type) do
query_factory do |query|
- query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_single
+ query.field :item, type, null: true, resolver: simple_resolver(test_object), authorize: permission_single
end
end
@@ -66,7 +66,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
permissions = permission_collection
query_factory do |qt|
- qt.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object } do
+ qt.field :item, type, null: true, resolver: simple_resolver(test_object) do
authorize permissions
end
end
@@ -79,7 +79,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe 'Field authorizations when field is a built in type' do
let(:query_type) do
query_factory do |query|
- query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object }
+ query.field :item, type, null: true, resolver: simple_resolver(test_object)
end
end
@@ -132,7 +132,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe 'Type authorizations' do
let(:query_type) do
query_factory do |query|
- query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object }
+ query.field :item, type, null: true, resolver: simple_resolver(test_object)
end
end
@@ -169,7 +169,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
query_factory do |query|
- query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_2
+ query.field :item, type, null: true, resolver: simple_resolver(test_object), authorize: permission_2
end
end
@@ -188,7 +188,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
query_factory do |query|
- query.field :item, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object, second_test_object] }
+ query.field :item, type.connection_type, null: true, resolver: simple_resolver([test_object, second_test_object])
end
end
@@ -208,9 +208,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe 'limiting connections with multiple objects' do
let(:query_type) do
query_factory do |query|
- query.field :item, type.connection_type, null: true, resolve: ->(obj, args, ctx) do
- [test_object, second_test_object]
- end
+ query.field :item, type.connection_type, null: true, resolver: simple_resolver([test_object, second_test_object])
end
end
@@ -234,7 +232,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
query_factory do |query|
- query.field :item, [type], null: true, resolve: ->(obj, args, ctx) { [test_object] }
+ query.field :item, [type], null: true, resolver: simple_resolver([test_object])
end
end
@@ -262,13 +260,13 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
type_factory do |type|
type.graphql_name 'FakeProjectType'
type.field :test_issues, issue_type.connection_type, null: false,
- resolve: -> (_, _, _) { Issue.where(project: [visible_project, other_project]).order(id: :asc) }
+ resolver: simple_resolver(Issue.where(project: [visible_project, other_project]).order(id: :asc))
end
end
let(:query_type) do
query_factory do |query|
- query.field :test_project, project_type, null: false, resolve: -> (_, _, _) { visible_project }
+ query.field :test_project, project_type, null: false, resolver: simple_resolver(visible_project)
end
end
diff --git a/spec/graphql/features/feature_flag_spec.rb b/spec/graphql/features/feature_flag_spec.rb
index 9ebc6e595a6..77810f78257 100644
--- a/spec/graphql/features/feature_flag_spec.rb
+++ b/spec/graphql/features/feature_flag_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe 'Graphql Field feature flags' do
let(:query_type) do
query_factory do |query|
- query.field :item, type, null: true, feature_flag: feature_flag, resolve: ->(obj, args, ctx) { test_object }
+ query.field :item, type, null: true, feature_flag: feature_flag, resolver: simple_resolver(test_object)
end
end
diff --git a/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb b/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb
index e6a7434d579..47ee338ad34 100644
--- a/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb
+++ b/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb
@@ -28,6 +28,7 @@ RSpec.describe Mutations::AlertManagement::CreateAlertIssue do
end
it_behaves_like 'an incident management tracked event', :incident_management_incident_created
+ it_behaves_like 'an incident management tracked event', :incident_management_alert_create_incident
end
context 'when CreateAlertIssue responds with an error' do
diff --git a/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb b/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb
index f74f9186743..acd7070d0d3 100644
--- a/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb
+++ b/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::Destroy do
specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
describe '#resolve' do
- subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+ subject(:resolve) { mutation_for(project, current_user).resolve(**args) }
context 'user has access to project' do
before do
diff --git a/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb b/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb
index d3ffb2abb47..96974c2aa6f 100644
--- a/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb
+++ b/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::ResetToken do
specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
describe '#resolve' do
- subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+ subject(:resolve) { mutation_for(project, current_user).resolve(**args) }
context 'user has sufficient access to project' do
before do
diff --git a/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb b/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb
index 45d92695e06..ddf23909035 100644
--- a/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb
+++ b/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken do
specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
describe '#resolve' do
- subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+ subject(:resolve) { mutation_for(project, current_user).resolve(**args) }
context 'user has sufficient access to project' do
before do
diff --git a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
index 08761ce64c2..8465393f299 100644
--- a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
+++ b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do
specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) }
describe '#resolve' do
- subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+ subject(:resolve) { mutation_for(project, current_user).resolve(**args) }
context 'user has access to project' do
before do
diff --git a/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb b/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb
index 71c43ed826c..24104a20465 100644
--- a/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb
+++ b/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb
@@ -34,18 +34,18 @@ RSpec.describe Mutations::Boards::Issues::IssueMoveList do
end
subject do
- mutation.resolve(params.merge(move_params))
+ mutation.resolve(**params.merge(move_params))
end
describe '#ready?' do
it 'raises an error if required arguments are missing' do
- expect { mutation.ready?(params) }
+ expect { mutation.ready?(**params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "At least one of the arguments " \
"fromListId, toListId, afterId or beforeId is required")
end
it 'raises an error if only one of fromListId and toListId is present' do
- expect { mutation.ready?(params.merge(from_list_id: list1.id)) }
+ expect { mutation.ready?(**params.merge(from_list_id: list1.id)) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError,
'Both fromListId and toListId must be present'
)
@@ -73,18 +73,6 @@ RSpec.describe Mutations::Boards::Issues::IssueMoveList do
it_behaves_like 'raises a resource not available error'
end
-
- context 'when user cannot access board' do
- let(:board) { create(:board, group: create(:group, :private)) }
-
- it_behaves_like 'raises a resource not available error'
- end
-
- context 'when passing board_id as nil' do
- let(:board) { nil }
-
- it_behaves_like 'raises a resource not available error'
- end
end
end
end
diff --git a/spec/graphql/mutations/boards/lists/create_spec.rb b/spec/graphql/mutations/boards/lists/create_spec.rb
index b1fe9911c7b..894dd1f34b4 100644
--- a/spec/graphql/mutations/boards/lists/create_spec.rb
+++ b/spec/graphql/mutations/boards/lists/create_spec.rb
@@ -23,12 +23,12 @@ RSpec.describe Mutations::Boards::Lists::Create do
describe '#ready?' do
it 'raises an error if required arguments are missing' do
- expect { mutation.ready?({ board_id: 'some id' }) }
+ expect { mutation.ready?(board_id: 'some id') }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
it 'raises an error if too many required arguments are specified' do
- expect { mutation.ready?({ board_id: 'some id', backlog: true, label_id: 'some label' }) }
+ expect { mutation.ready?(board_id: 'some id', backlog: true, label_id: 'some label') }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
end
@@ -68,9 +68,8 @@ RSpec.describe Mutations::Boards::Lists::Create do
context 'when label not found' do
let(:list_create_params) { { label_id: "gid://gitlab/Label/#{non_existing_record_id}" } }
- it 'raises an error' do
- expect { subject }
- .to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'Label not found!')
+ it 'returns an error' do
+ expect(subject[:errors]).to include 'Label not found'
end
end
end
diff --git a/spec/graphql/mutations/concerns/mutations/finds_by_gid_spec.rb b/spec/graphql/mutations/concerns/mutations/finds_by_gid_spec.rb
new file mode 100644
index 00000000000..37e0fd611e4
--- /dev/null
+++ b/spec/graphql/mutations/concerns/mutations/finds_by_gid_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::FindsByGid do
+ include GraphqlHelpers
+
+ let(:mutation_class) do
+ Class.new(Mutations::BaseMutation) do
+ authorize :read_user
+
+ include Mutations::FindsByGid
+ end
+ end
+
+ let(:query) { double('Query', schema: GitlabSchema) }
+ let(:context) { GraphQL::Query::Context.new(query: query, object: nil, values: { current_user: user }) }
+ let(:user) { create(:user) }
+ let(:gid) { user.to_global_id }
+
+ subject(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
+
+ it 'calls GitlabSchema.find_by_gid to find objects during authorized_find!' do
+ expect(mutation.authorized_find!(id: gid)).to eq(user)
+ end
+end
diff --git a/spec/graphql/mutations/container_expiration_policies/update_spec.rb b/spec/graphql/mutations/container_expiration_policies/update_spec.rb
index 9c6016e0af4..e22fb951172 100644
--- a/spec/graphql/mutations/container_expiration_policies/update_spec.rb
+++ b/spec/graphql/mutations/container_expiration_policies/update_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do
specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
describe '#resolve' do
- subject { described_class.new(object: project, context: { current_user: user }, field: nil).resolve(params) }
+ subject { described_class.new(object: project, context: { current_user: user }, field: nil).resolve(**params) }
RSpec.shared_examples 'returning a success' do
it 'returns the container expiration policy with no errors' do
diff --git a/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb b/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb
new file mode 100644
index 00000000000..f22d9ffe753
--- /dev/null
+++ b/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::ContainerRepositories::DestroyTags do
+ include_context 'container repository delete tags service shared context'
+ using RSpec::Parameterized::TableSyntax
+
+ let(:id) { repository.to_global_id.to_s }
+
+ specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
+
+ describe '#resolve' do
+ let(:tags) { %w[A C D E] }
+
+ subject do
+ described_class.new(object: nil, context: { current_user: user }, field: nil)
+ .resolve(id: id, tag_names: tags)
+ end
+
+ shared_examples 'destroying container repository tags' do
+ before do
+ stub_delete_reference_requests(tags)
+ expect_delete_tag_by_names(tags)
+ allow_next_instance_of(ContainerRegistry::Client) do |client|
+ allow(client).to receive(:supports_tag_delete?).and_return(true)
+ end
+ end
+
+ it 'destroys the container repository tags' do
+ expect(Projects::ContainerRepository::DeleteTagsService)
+ .to receive(:new).and_call_original
+
+ expect(subject).to eq(errors: [], deleted_tag_names: tags)
+ end
+
+ it 'creates a package event' do
+ expect(::Packages::CreateEventService)
+ .to receive(:new).with(nil, user, event_name: :delete_tag_bulk, scope: :tag).and_call_original
+ expect { subject }.to change { ::Packages::Event.count }.by(1)
+ end
+ end
+
+ shared_examples 'denying access to container respository' do
+ it 'raises an error' do
+ expect(::Projects::ContainerRepository::DeleteTagsService).not_to receive(:new)
+
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'with valid id' do
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'destroying container repository tags'
+ :developer | 'destroying container repository tags'
+ :reporter | 'denying access to container respository'
+ :guest | 'denying access to container respository'
+ :anonymous | 'denying access to container respository'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", user) unless user_role == :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
+
+ context 'with invalid id' do
+ let(:id) { 'gid://gitlab/ContainerRepository/5555' }
+
+ it_behaves_like 'denying access to container respository'
+ end
+
+ context 'with service error' do
+ before do
+ project.add_maintainer(user)
+ allow_next_instance_of(Projects::ContainerRepository::DeleteTagsService) do |service|
+ allow(service).to receive(:execute).and_return(message: 'could not delete tags', status: :error)
+ end
+ end
+
+ it { is_expected.to eq(errors: ['could not delete tags'], deleted_tag_names: []) }
+
+ it 'does not create a package event' do
+ expect(::Packages::CreateEventService).not_to receive(:new)
+ expect { subject }.not_to change { ::Packages::Event.count }
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
index 2e5d41a8f1e..162b1249ab5 100644
--- a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
+++ b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Mutations::Discussions::ToggleResolve do
describe '#resolve' do
subject do
- mutation.resolve({ id: id_arg, resolve: resolve_arg })
+ mutation.resolve(id: id_arg, resolve: resolve_arg)
end
let(:id_arg) { discussion.to_global_id.to_s }
diff --git a/spec/graphql/mutations/environments/canary_ingress/update_spec.rb b/spec/graphql/mutations/environments/canary_ingress/update_spec.rb
new file mode 100644
index 00000000000..c022828cf09
--- /dev/null
+++ b/spec/graphql/mutations/environments/canary_ingress/update_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Environments::CanaryIngress::Update do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let(:user) { maintainer }
+
+ subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+
+ before_all do
+ project.add_maintainer(maintainer)
+ project.add_reporter(reporter)
+ end
+
+ describe '#resolve' do
+ subject { mutation.resolve(id: environment_id, weight: weight) }
+
+ let(:environment_id) { environment.to_global_id.to_s }
+ let(:weight) { 50 }
+ let(:update_service) { double('update_service') }
+
+ before do
+ allow(Environments::CanaryIngress::UpdateService).to receive(:new) { update_service }
+ end
+
+ context 'when service execution succeeded' do
+ before do
+ allow(update_service).to receive(:execute_async) { { status: :success } }
+ end
+
+ it 'returns no errors' do
+ expect(subject[:errors]).to be_empty
+ end
+ end
+
+ context 'when service encounters a problem' do
+ before do
+ allow(update_service).to receive(:execute_async) { { status: :error, message: 'something went wrong' } }
+ end
+
+ it 'returns an error' do
+ expect(subject[:errors]).to eq(['something went wrong'])
+ end
+ end
+
+ context 'when environment is not found' do
+ let(:environment_id) { non_existing_record_id.to_s }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(GraphQL::CoercionError)
+ end
+ end
+
+ context 'when user is reporter who does not have permission to access the environment' do
+ let(:user) { reporter }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error("The resource that you are attempting to access does not exist or you don't have permission to perform this action")
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/issues/create_spec.rb b/spec/graphql/mutations/issues/create_spec.rb
index 57658f6b358..422ad40a9cb 100644
--- a/spec/graphql/mutations/issues/create_spec.rb
+++ b/spec/graphql/mutations/issues/create_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe Mutations::Issues::Create do
project.add_guest(assignee2)
end
- subject { mutation.resolve(mutation_params) }
+ subject { mutation.resolve(**mutation_params) }
context 'when the user does not have permission to create an issue' do
it 'raises an error' do
@@ -117,7 +117,7 @@ RSpec.describe Mutations::Issues::Create do
end
it 'raises exception when mutually exclusive params are given' do
- expect { mutation.ready?(mutation_params) }
+ expect { mutation.ready?(**mutation_params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
end
@@ -128,7 +128,7 @@ RSpec.describe Mutations::Issues::Create do
end
it 'raises exception when mutually exclusive params are given' do
- expect { mutation.ready?(mutation_params) }
+ expect { mutation.ready?(**mutation_params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /to resolve a discussion please also provide `merge_request_to_resolve_discussions_of` parameter/)
end
end
@@ -139,7 +139,7 @@ RSpec.describe Mutations::Issues::Create do
end
it 'raises exception when mutually exclusive params are given' do
- expect { mutation.ready?(mutation_params) }.not_to raise_error
+ expect { mutation.ready?(**mutation_params) }.not_to raise_error
end
end
end
diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb
index ce1eb874bcf..f10e257e153 100644
--- a/spec/graphql/mutations/issues/update_spec.rb
+++ b/spec/graphql/mutations/issues/update_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Mutations::Issues::Update do
}.merge(expected_attributes)
end
- subject { mutation.resolve(mutation_params) }
+ subject { mutation.resolve(**mutation_params) }
it_behaves_like 'permission level for issue mutation is correctly verified'
diff --git a/spec/graphql/mutations/labels/create_spec.rb b/spec/graphql/mutations/labels/create_spec.rb
index 8b284816d63..b2dd94f31bb 100644
--- a/spec/graphql/mutations/labels/create_spec.rb
+++ b/spec/graphql/mutations/labels/create_spec.rb
@@ -58,7 +58,7 @@ RSpec.describe Mutations::Labels::Create do
end
describe '#ready?' do
- subject { mutation.ready?(attributes.merge(extra_params)) }
+ subject { mutation.ready?(**attributes.merge(extra_params)) }
context 'when passing both project_path and group_path' do
let(:extra_params) { { project_path: 'foo', group_path: 'bar' } }
diff --git a/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb b/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb
index 8c22e1a1cb6..d88b196cbff 100644
--- a/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb
+++ b/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Mutations::Notes::RepositionImageDiffNote do
describe '#resolve' do
subject do
- mutation.resolve({ note: note, position: new_position })
+ mutation.resolve(note: note, position: new_position)
end
let_it_be(:noteable) { create(:merge_request) }
diff --git a/spec/graphql/mutations/releases/delete_spec.rb b/spec/graphql/mutations/releases/delete_spec.rb
new file mode 100644
index 00000000000..bedb72b002c
--- /dev/null
+++ b/spec/graphql/mutations/releases/delete_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Releases::Delete do
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:non_project_member) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:tag) { 'v1.1.0'}
+ let_it_be(:release) { create(:release, project: project, tag: tag) }
+
+ let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
+
+ let(:mutation_arguments) do
+ {
+ project_path: project.full_path,
+ tag: tag
+ }
+ end
+
+ before do
+ project.add_developer(developer)
+ project.add_maintainer(maintainer)
+ end
+
+ shared_examples 'unauthorized or not found error' do
+ it 'raises a Gitlab::Graphql::Errors::ResourceNotAvailable error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable, "The resource that you are attempting to access does not exist or you don't have permission to perform this action")
+ end
+ end
+
+ describe '#resolve' do
+ subject(:resolve) do
+ mutation.resolve(**mutation_arguments)
+ end
+
+ context 'when the current user has access to create releases' do
+ let(:current_user) { maintainer }
+
+ it 'deletes the release' do
+ expect { subject }.to change { Release.count }.by(-1)
+ end
+
+ it 'returns the deleted release' do
+ expect(subject[:release].tag).to eq(tag)
+ end
+
+ it 'does not remove the Git tag associated with the deleted release' do
+ expect { subject }.not_to change { Project.find_by_id(project.id).repository.tag_count }
+ end
+
+ it 'returns no errors' do
+ expect(subject[:errors]).to eq([])
+ end
+
+ context 'validation' do
+ context 'when the release does not exist' do
+ let(:mutation_arguments) { super().merge(tag: 'not-a-real-release') }
+
+ it 'returns the release as nil' do
+ expect(subject[:release]).to be_nil
+ end
+
+ it 'returns an errors-at-data message' do
+ expect(subject[:errors]).to eq(['Release does not exist'])
+ end
+ end
+
+ context 'when the project does not exist' do
+ let(:mutation_arguments) { super().merge(project_path: 'not/a/real/path') }
+
+ it_behaves_like 'unauthorized or not found error'
+ end
+ end
+ end
+
+ context "when the current user doesn't have access to update releases" do
+ context 'when the user is a developer' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'unauthorized or not found error'
+ end
+
+ context 'when the user is a non-project member' do
+ let(:current_user) { non_project_member }
+
+ it_behaves_like 'unauthorized or not found error'
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/releases/update_spec.rb b/spec/graphql/mutations/releases/update_spec.rb
new file mode 100644
index 00000000000..0406e9c96f3
--- /dev/null
+++ b/spec/graphql/mutations/releases/update_spec.rb
@@ -0,0 +1,232 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Releases::Update do
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:milestone_12_3) { create(:milestone, project: project, title: '12.3') }
+ let_it_be(:milestone_12_4) { create(:milestone, project: project, title: '12.4') }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+
+ let_it_be(:tag) { 'v1.1.0'}
+ let_it_be(:name) { 'Version 1.0'}
+ let_it_be(:description) { 'The first release :rocket:' }
+ let_it_be(:released_at) { Time.parse('2018-12-10').utc }
+ let_it_be(:created_at) { Time.parse('2018-11-05').utc }
+ let_it_be(:milestones) { [milestone_12_3.title, milestone_12_4.title] }
+
+ let_it_be(:release) do
+ create(:release, project: project, tag: tag, name: name,
+ description: description, released_at: released_at,
+ created_at: created_at, milestones: [milestone_12_3, milestone_12_4])
+ end
+
+ let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
+
+ let(:mutation_arguments) do
+ {
+ project_path: project.full_path,
+ tag: tag
+ }
+ end
+
+ around do |example|
+ freeze_time { example.run }
+ end
+
+ before do
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+ end
+
+ shared_examples 'no changes to the release except for the' do |except_for|
+ it 'does not change other release properties' do
+ expect(updated_release.project).to eq(project)
+ expect(updated_release.tag).to eq(tag)
+
+ expect(updated_release.name).to eq(name) unless except_for == :name
+ expect(updated_release.description).to eq(description) unless except_for == :description
+ expect(updated_release.released_at).to eq(released_at) unless except_for == :released_at
+
+ # Right now the milestones are returned in a non-deterministic order.
+ # Because of this, we need to allow for milestones to be returned in any order.
+ # Once https://gitlab.com/gitlab-org/gitlab/-/issues/259012 has been
+ # fixed, this can be updated to expect a specific order.
+ expect(updated_release.milestones).to match_array([milestone_12_3, milestone_12_4]) unless except_for == :milestones
+ end
+ end
+
+ shared_examples 'validation error with message' do |message|
+ it 'returns the updated release as nil' do
+ expect(updated_release).to be_nil
+ end
+
+ it 'returns a validation error' do
+ expect(subject[:errors]).to eq([message])
+ end
+ end
+
+ describe '#ready?' do
+ let(:current_user) { developer }
+
+ subject(:ready) do
+ mutation.ready?(**mutation_arguments)
+ end
+
+ context 'when released_at is included as an argument but is passed nil' do
+ let(:mutation_arguments) { super().merge(released_at: nil) }
+
+ it 'raises a validation error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'if the releasedAt argument is provided, it cannot be null')
+ end
+ end
+
+ context 'when milestones is included as an argument but is passed nil' do
+ let(:mutation_arguments) { super().merge(milestones: nil) }
+
+ it 'raises a validation error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'if the milestones argument is provided, it cannot be null')
+ end
+ end
+ end
+
+ describe '#resolve' do
+ subject(:resolve) do
+ mutation.resolve(**mutation_arguments)
+ end
+
+ let(:updated_release) { subject[:release] }
+
+ context 'when the current user has access to create releases' do
+ let(:current_user) { developer }
+
+ context 'name' do
+ let(:mutation_arguments) { super().merge(name: updated_name) }
+
+ context 'when a new name is provided' do
+ let(:updated_name) { 'Updated name' }
+
+ it 'updates the name' do
+ expect(updated_release.name).to eq(updated_name)
+ end
+
+ it_behaves_like 'no changes to the release except for the', :name
+ end
+
+ context 'when nil is provided' do
+ let(:updated_name) { nil }
+
+ it 'updates the name to be the tag name' do
+ expect(updated_release.name).to eq(tag)
+ end
+
+ it_behaves_like 'no changes to the release except for the', :name
+ end
+ end
+
+ context 'description' do
+ let(:mutation_arguments) { super().merge(description: updated_description) }
+
+ context 'when a new description is provided' do
+ let(:updated_description) { 'Updated description' }
+
+ it 'updates the description' do
+ expect(updated_release.description).to eq(updated_description)
+ end
+
+ it_behaves_like 'no changes to the release except for the', :description
+ end
+
+ context 'when nil is provided' do
+ let(:updated_description) { nil }
+
+ it 'updates the description to nil' do
+ expect(updated_release.description).to eq(nil)
+ end
+
+ it_behaves_like 'no changes to the release except for the', :description
+ end
+ end
+
+ context 'released_at' do
+ let(:mutation_arguments) { super().merge(released_at: updated_released_at) }
+
+ context 'when a new released_at is provided' do
+ let(:updated_released_at) { Time.parse('2020-12-10').utc }
+
+ it 'updates the released_at' do
+ expect(updated_release.released_at).to eq(updated_released_at)
+ end
+
+ it_behaves_like 'no changes to the release except for the', :released_at
+ end
+ end
+
+ context 'milestones' do
+ let(:mutation_arguments) { super().merge(milestones: updated_milestones) }
+
+ context 'when a new set of milestones is provided provided' do
+ let(:updated_milestones) { [milestone_12_3.title] }
+
+ it 'updates the milestone associations' do
+ expect(updated_release.milestones).to eq([milestone_12_3])
+ end
+
+ it_behaves_like 'no changes to the release except for the', :milestones
+ end
+
+ context 'when an empty array is provided' do
+ let(:updated_milestones) { [] }
+
+ it 'removes all milestone associations' do
+ expect(updated_release.milestones).to eq([])
+ end
+
+ it_behaves_like 'no changes to the release except for the', :milestones
+ end
+
+ context 'when a non-existent milestone title is provided' do
+ let(:updated_milestones) { ['not real'] }
+
+ it_behaves_like 'validation error with message', 'Milestone(s) not found: not real'
+ end
+
+ context 'when a milestone title from a different project is provided' do
+ let(:milestone_in_different_project) { create(:milestone, title: 'milestone in different project') }
+ let(:updated_milestones) { [milestone_in_different_project.title] }
+
+ it_behaves_like 'validation error with message', 'Milestone(s) not found: milestone in different project'
+ end
+ end
+
+ context 'validation' do
+ context 'when no updated fields are provided' do
+ it_behaves_like 'validation error with message', 'params is empty'
+ end
+
+ context 'when the tag does not exist' do
+ let(:mutation_arguments) { super().merge(tag: 'not-a-real-tag') }
+
+ it_behaves_like 'validation error with message', 'Tag does not exist'
+ end
+
+ context 'when the project does not exist' do
+ let(:mutation_arguments) { super().merge(project_path: 'not/a/real/path') }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable, "The resource that you are attempting to access does not exist or you don't have permission to perform this action")
+ end
+ end
+ end
+ end
+
+ context "when the current user doesn't have access to update releases" do
+ let(:current_user) { reporter }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable, "The resource that you are attempting to access does not exist or you don't have permission to perform this action")
+ end
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
index 42830f0024d..c042f6dac19 100644
--- a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
+++ b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
@@ -33,11 +33,21 @@ RSpec.describe Resolvers::AlertManagement::AlertResolver do
end
context 'finding by status' do
- let(:args) { { status: [Types::AlertManagement::StatusEnum.values['IGNORED'].value] } }
+ let(:args) { { statuses: [Types::AlertManagement::StatusEnum.values['IGNORED'].value] } }
it { is_expected.to contain_exactly(ignored_alert) }
end
+ context 'filtering by domain' do
+ let_it_be(:alert1) { create(:alert_management_alert, project: project, monitoring_tool: 'Cilium', domain: :threat_monitoring) }
+ let_it_be(:alert2) { create(:alert_management_alert, project: project, monitoring_tool: 'Cilium', domain: :threat_monitoring) }
+ let_it_be(:alert3) { create(:alert_management_alert, project: project, monitoring_tool: 'generic') }
+
+ let(:args) { { domain: 'operations' } }
+
+ it { is_expected.to contain_exactly(resolved_alert, ignored_alert, alert3) }
+ end
+
describe 'sorting' do
# Other sorting examples in spec/finders/alert_management/alerts_finder_spec.rb
context 'when sorting by events count' do
diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb
index e5b9fb57e42..8a24b69eb6f 100644
--- a/spec/graphql/resolvers/base_resolver_spec.rb
+++ b/spec/graphql/resolvers/base_resolver_spec.rb
@@ -273,4 +273,12 @@ RSpec.describe Resolvers::BaseResolver do
end
end
end
+
+ describe '#offset_pagination' do
+ let(:instance) { resolver_instance(resolver) }
+
+ it 'is sugar for OffsetActiveRecordRelationConnection.new' do
+ expect(instance.offset_pagination(User.none)).to be_a(::Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection)
+ end
+ end
end
diff --git a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb
index 4ccf194522f..e7c56a526f4 100644
--- a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
end
it 'finds only issues matching filters' do
- result = resolve_board_list_issues(args: { filters: { label_name: label.title, not: { label_name: label2.title } } }).items
+ result = resolve_board_list_issues(args: { filters: { label_name: [label.title], not: { label_name: [label2.title] } } }).items
expect(result).to match_array([issue1, issue3])
end
diff --git a/spec/graphql/resolvers/board_lists_resolver_spec.rb b/spec/graphql/resolvers/board_lists_resolver_spec.rb
index c1d8041a1e0..71ebec4dc7e 100644
--- a/spec/graphql/resolvers/board_lists_resolver_spec.rb
+++ b/spec/graphql/resolvers/board_lists_resolver_spec.rb
@@ -29,9 +29,7 @@ RSpec.describe Resolvers::BoardListsResolver do
context 'with unauthorized user' do
it 'raises an error' do
- expect do
- resolve_board_lists(current_user: unauth_user)
- end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ expect(resolve_board_lists(current_user: unauth_user)).to be_nil
end
end
@@ -101,12 +99,6 @@ RSpec.describe Resolvers::BoardListsResolver do
end
def resolve_board_lists(args: {}, current_user: user)
- context = GraphQL::Query::Context.new(
- query: OpenStruct.new(schema: nil),
- values: { current_user: current_user },
- object: nil
- )
-
- resolve(described_class, obj: board, args: args, ctx: context )
+ resolve(described_class, obj: board, args: args, ctx: { current_user: current_user })
end
end
diff --git a/spec/graphql/resolvers/ci/config_resolver_spec.rb b/spec/graphql/resolvers/ci/config_resolver_spec.rb
new file mode 100644
index 00000000000..6911acdb4ec
--- /dev/null
+++ b/spec/graphql/resolvers/ci/config_resolver_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Ci::ConfigResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ before do
+ yaml_processor_double = instance_double(::Gitlab::Ci::YamlProcessor)
+ allow(yaml_processor_double).to receive(:execute).and_return(fake_result)
+
+ allow(::Gitlab::Ci::YamlProcessor).to receive(:new).and_return(yaml_processor_double)
+ end
+
+ context 'with a valid .gitlab-ci.yml' do
+ let(:fake_result) do
+ ::Gitlab::Ci::YamlProcessor::Result.new(
+ ci_config: ::Gitlab::Ci::Config.new(content),
+ errors: [],
+ warnings: []
+ )
+ end
+
+ let_it_be(:content) do
+ File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
+ end
+
+ it 'lints the ci config file' do
+ response = resolve(described_class, args: { content: content }, ctx: {})
+
+ expect(response[:status]).to eq(:valid)
+ expect(response[:errors]).to be_empty
+ end
+ end
+
+ context 'with an invalid .gitlab-ci.yml' do
+ let(:content) { 'invalid' }
+
+ let(:fake_result) do
+ Gitlab::Ci::YamlProcessor::Result.new(
+ ci_config: nil,
+ errors: ['Invalid configuration format'],
+ warnings: []
+ )
+ end
+
+ it 'responds with errors about invalid syntax' do
+ response = resolve(described_class, args: { content: content }, ctx: {})
+
+ expect(response[:status]).to eq(:invalid)
+ expect(response[:errors]).to eq(['Invalid configuration format'])
+ end
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/ci/jobs_resolver_spec.rb b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb
index a836c89bd61..46ee74a5f7e 100644
--- a/spec/graphql/resolvers/ci/jobs_resolver_spec.rb
+++ b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb
@@ -17,10 +17,14 @@ RSpec.describe Resolvers::Ci::JobsResolver do
describe '#resolve' do
context 'when security_report_types is empty' do
it "returns all of the pipeline's jobs" do
- jobs = resolve(described_class, obj: pipeline, args: {}, ctx: {})
-
- job_names = jobs.map(&:name)
- expect(job_names).to contain_exactly('Normal job', 'DAST job', 'SAST job', 'Container scanning job')
+ jobs = resolve(described_class, obj: pipeline)
+
+ expect(jobs).to contain_exactly(
+ have_attributes(name: 'Normal job'),
+ have_attributes(name: 'DAST job'),
+ have_attributes(name: 'SAST job'),
+ have_attributes(name: 'Container scanning job')
+ )
end
end
@@ -30,10 +34,12 @@ RSpec.describe Resolvers::Ci::JobsResolver do
::Types::Security::ReportTypeEnum.values['SAST'].value,
::Types::Security::ReportTypeEnum.values['DAST'].value
]
- jobs = resolve(described_class, obj: pipeline, args: { security_report_types: report_types }, ctx: {})
+ jobs = resolve(described_class, obj: pipeline, args: { security_report_types: report_types })
- job_names = jobs.map(&:name)
- expect(job_names).to contain_exactly('DAST job', 'SAST job')
+ expect(jobs).to contain_exactly(
+ have_attributes(name: 'DAST job'),
+ have_attributes(name: 'SAST job')
+ )
end
end
end
diff --git a/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb b/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
index a408981c08e..241a9e58147 100644
--- a/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
+++ b/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
@@ -50,6 +50,6 @@ RSpec.describe Resolvers::CommitPipelinesResolver do
it 'resolves pipelines for commit and ref' do
pipelines = resolve_pipelines
- expect(pipelines).to eq([pipeline2, pipeline])
+ expect(pipelines.to_a).to eq([pipeline2, pipeline])
end
end
diff --git a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
index b6fe94a2312..5370f7a7433 100644
--- a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
+++ b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
@@ -4,18 +4,25 @@ require 'spec_helper'
RSpec.describe ::CachingArrayResolver do
include GraphqlHelpers
+ include Gitlab::Graphql::Laziness
- let_it_be(:non_admins) { create_list(:user, 4, admin: false) }
- let(:query_context) { {} }
+ let_it_be(:admins) { create_list(:user, 4, admin: true) }
+ let(:query_context) { { current_user: admins.first } }
let(:max_page_size) { 10 }
let(:field) { double('Field', max_page_size: max_page_size) }
- let(:schema) { double('Schema', default_max_page_size: 3) }
+ let(:schema) do
+ Class.new(GitlabSchema) do
+ default_max_page_size 3
+ end
+ end
let_it_be(:caching_resolver) do
mod = described_class
Class.new(::Resolvers::BaseResolver) do
include mod
+ type [::Types::UserType], null: true
+ argument :is_admin, ::GraphQL::BOOLEAN_TYPE, required: false
def query_input(is_admin:)
is_admin
@@ -44,6 +51,8 @@ RSpec.describe ::CachingArrayResolver do
Class.new(::Resolvers::BaseResolver) do
include mod
+ type [::Types::UserType], null: true
+ argument :username, ::GraphQL::STRING_TYPE, required: false
def query_input(username:)
username
@@ -72,7 +81,7 @@ RSpec.describe ::CachingArrayResolver do
expect(User).to receive(:from_union).twice.and_call_original
results = users.in_groups_of(2, false).map do |users|
- resolve(resolver, args: { username: users.map(&:username) }, field: field, schema: schema)
+ resolve(resolver, args: { username: users.map(&:username) }, schema: schema)
end
expect(results.flat_map(&method(:force))).to match_array(users)
@@ -80,19 +89,19 @@ RSpec.describe ::CachingArrayResolver do
end
context 'all queries return results' do
- let_it_be(:admins) { create_list(:admin, 3) }
+ let_it_be(:non_admins) { create_list(:user, 3, admin: false) }
it 'batches the queries' do
expect do
- [resolve_users(true), resolve_users(false)].each(&method(:force))
- end.to issue_same_number_of_queries_as { force(resolve_users(nil)) }
+ [resolve_users(admin: true), resolve_users(admin: false)].each(&method(:force))
+ end.to issue_same_number_of_queries_as { force(resolve_users(admin: nil)) }
end
it 'finds the correct values' do
- found_admins = resolve_users(true)
- found_others = resolve_users(false)
- admins_again = resolve_users(true)
- found_all = resolve_users(nil)
+ found_admins = resolve_users(admin: true)
+ found_others = resolve_users(admin: false)
+ admins_again = resolve_users(admin: true)
+ found_all = resolve_users(admin: nil)
expect(force(found_admins)).to match_array(admins)
expect(force(found_others)).to match_array(non_admins)
@@ -104,37 +113,37 @@ RSpec.describe ::CachingArrayResolver do
it 'does not perform a union of a query with itself' do
expect(User).to receive(:where).once.and_call_original
- [resolve_users(false), resolve_users(false)].each(&method(:force))
+ [resolve_users(admin: false), resolve_users(admin: false)].each(&method(:force))
end
context 'one of the queries returns no results' do
it 'finds the correct values' do
- found_admins = resolve_users(true)
- found_others = resolve_users(false)
- found_all = resolve_users(nil)
+ found_admins = resolve_users(admin: true)
+ found_others = resolve_users(admin: false)
+ found_all = resolve_users(admin: nil)
- expect(force(found_admins)).to be_empty
- expect(force(found_others)).to match_array(non_admins)
- expect(force(found_all)).to match_array(non_admins)
+ expect(force(found_admins)).to match_array(admins)
+ expect(force(found_others)).to be_empty
+ expect(force(found_all)).to match_array(admins)
end
end
context 'one of the queries has already been cached' do
before do
- force(resolve_users(nil))
+ force(resolve_users(admin: nil))
end
it 'avoids further queries' do
expect do
- repeated_find = resolve_users(nil)
+ repeated_find = resolve_users(admin: nil)
- expect(force(repeated_find)).to match_array(non_admins)
+ expect(force(repeated_find)).to match_array(admins)
end.not_to exceed_query_limit(0)
end
end
context 'the resolver overrides item_found' do
- let_it_be(:admins) { create_list(:admin, 2) }
+ let_it_be(:non_admins) { create_list(:user, 2, admin: false) }
let(:query_context) do
{
found: { true => [], false => [], nil => [] }
@@ -150,14 +159,14 @@ RSpec.describe ::CachingArrayResolver do
end
it 'receives item_found for each key the item mapped to' do
- found_admins = resolve_users(true, with_item_found)
- found_all = resolve_users(nil, with_item_found)
+ found_admins = resolve_users(admin: true, resolver: with_item_found)
+ found_all = resolve_users(admin: nil, resolver: with_item_found)
[found_admins, found_all].each(&method(:force))
expect(query_context[:found]).to match({
- false => be_empty,
true => match_array(admins),
+ false => be_empty,
nil => match_array(admins + non_admins)
})
end
@@ -167,11 +176,11 @@ RSpec.describe ::CachingArrayResolver do
let(:max_page_size) { 2 }
it 'respects the max_page_size, on a per subset basis' do
- found_all = resolve_users(nil)
- found_others = resolve_users(false)
+ found_all = resolve_users(admin: nil)
+ found_admins = resolve_users(admin: true)
expect(force(found_all).size).to eq(2)
- expect(force(found_others).size).to eq(2)
+ expect(force(found_admins).size).to eq(2)
end
end
@@ -179,11 +188,11 @@ RSpec.describe ::CachingArrayResolver do
let(:max_page_size) { nil }
it 'takes the page size from schema.default_max_page_size' do
- found_all = resolve_users(nil)
- found_others = resolve_users(false)
+ found_all = resolve_users(admin: nil)
+ found_admins = resolve_users(admin: true)
expect(force(found_all).size).to eq(schema.default_max_page_size)
- expect(force(found_others).size).to eq(schema.default_max_page_size)
+ expect(force(found_admins).size).to eq(schema.default_max_page_size)
end
end
@@ -197,12 +206,10 @@ RSpec.describe ::CachingArrayResolver do
end
end
- def resolve_users(is_admin, resolver = caching_resolver)
- args = { is_admin: is_admin }
- resolve(resolver, args: args, field: field, ctx: query_context, schema: schema)
- end
-
- def force(lazy)
- ::Gitlab::Graphql::Lazy.force(lazy)
+ def resolve_users(admin:, resolver: caching_resolver)
+ args = { is_admin: admin }
+ opts = resolver.field_options
+ allow(resolver).to receive(:field_options).and_return(opts.merge(max_page_size: max_page_size))
+ resolve(resolver, args: args, ctx: query_context, schema: schema, field: field)
end
end
diff --git a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
index ebea9e5522b..27ac1572cab 100644
--- a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
+++ b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe LooksAhead do
# Simplified schema to test lookahead
let_it_be(:schema) do
- issues_resolver = Class.new(Resolvers::BaseResolver) do
+ issues_resolver = Class.new(GraphQL::Schema::Resolver) do
include LooksAhead
def resolve_with_lookahead(**args)
@@ -41,7 +41,6 @@ RSpec.describe LooksAhead do
field :issues, issue.connection_type,
null: true
field :issues_with_lookahead, issue.connection_type,
- extras: [:lookahead],
resolver: issues_resolver,
null: true
end
diff --git a/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb
index 850b9f8cc87..cc7e2f6814a 100644
--- a/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Resolvers::DesignManagement::Version::DesignAtVersionResolver do
let(:all_singular_args) do
{
- design_at_version_id: global_id_of(dav(design)),
+ id: global_id_of(dav(design)),
design_id: global_id_of(design),
filename: design.filename
}
@@ -50,7 +50,7 @@ RSpec.describe Resolvers::DesignManagement::Version::DesignAtVersionResolver do
end
end
- %i[design_at_version_id design_id filename].each do |arg|
+ %i[id design_id filename].each do |arg|
describe "passing #{arg}" do
let(:args) { all_singular_args.slice(arg) }
@@ -71,7 +71,7 @@ RSpec.describe Resolvers::DesignManagement::Version::DesignAtVersionResolver do
describe 'attempting to retrieve an object not visible at this version' do
let(:design) { design_d }
- %i[design_at_version_id design_id filename].each do |arg|
+ %i[id design_id filename].each do |arg|
describe "passing #{arg}" do
let(:args) { all_singular_args.slice(arg) }
diff --git a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
index 403261fc22a..b0fc78af2af 100644
--- a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Resolvers::DesignManagement::VersionInCollectionResolver do
end
context 'we pass an id' do
- let(:params) { { version_id: global_id_of(first_version) } }
+ let(:params) { { id: global_id_of(first_version) } }
it { is_expected.to eq(first_version) }
end
@@ -44,13 +44,13 @@ RSpec.describe Resolvers::DesignManagement::VersionInCollectionResolver do
end
context 'we pass an inconsistent mixture of sha and version id' do
- let(:params) { { sha: first_version.sha, version_id: global_id_of(create(:design_version)) } }
+ let(:params) { { sha: first_version.sha, id: global_id_of(create(:design_version)) } }
it { is_expected.to be_nil }
end
context 'we pass the id of something that is not a design_version' do
- let(:params) { { version_id: global_id_of(project) } }
+ let(:params) { { id: global_id_of(project) } }
let(:appropriate_error) { ::GraphQL::CoercionError }
it 'raises an appropriate error' do
diff --git a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
index 5bc1c555e9a..23d4d86c79a 100644
--- a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do
end
shared_examples 'a source of versions' do
- subject(:result) { resolve_versions(object) }
+ subject(:result) { resolve_versions(object)&.to_a }
let_it_be(:all_versions) { object.versions.ordered }
@@ -39,7 +39,7 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do
context 'without constraints' do
it 'returns the ordered versions' do
- expect(result).to eq(all_versions)
+ expect(result.to_a).to eq(all_versions)
end
end
@@ -51,13 +51,13 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do
end
context 'by earlier_or_equal_to_id' do
- let(:params) { { id: global_id_of(first_version) } }
+ let(:params) { { earlier_or_equal_to_id: global_id_of(first_version) } }
it_behaves_like 'a query for all_versions up to the first_version'
end
context 'by earlier_or_equal_to_sha' do
- let(:params) { { sha: first_version.sha } }
+ let(:params) { { earlier_or_equal_to_sha: first_version.sha } }
it_behaves_like 'a query for all_versions up to the first_version'
end
@@ -68,8 +68,8 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do
# return successfully
let(:params) do
{
- sha: first_version.sha,
- id: global_id_of(first_version)
+ earlier_or_equal_to_sha: first_version.sha,
+ earlier_or_equal_to_id: global_id_of(first_version)
}
end
@@ -79,8 +79,8 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do
context 'and they do not match' do
let(:params) do
{
- sha: first_version.sha,
- id: global_id_of(other_version)
+ earlier_or_equal_to_sha: first_version.sha,
+ earlier_or_equal_to_id: global_id_of(other_version)
}
end
@@ -111,7 +111,7 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do
end
def resolve_versions(obj, context = { current_user: current_user })
- eager_resolve(resolver, obj: obj, args: params.merge(parent: parent), ctx: context)
+ eager_resolve(resolver, obj: obj, parent: parent, args: params, ctx: context)
end
end
end
diff --git a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
index edca11f40d7..170a602fb0d 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
@@ -88,8 +88,8 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do
it 'sets the pagination variables' do
result = resolve_errors
- expect(result.next_cursor).to eq 'next'
- expect(result.previous_cursor).to eq 'prev'
+ expect(result.end_cursor).to eq 'next'
+ expect(result.start_cursor).to eq 'prev'
end
it 'returns an externally paginated array' do
diff --git a/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb b/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb
index 16eb190efc6..decc3569d6c 100644
--- a/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb
+++ b/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb
@@ -62,22 +62,13 @@ RSpec.describe Resolvers::IssueStatusCountsResolver do
end
it 'filters by issue type', :aggregate_failures do
- result = resolve_issue_status_counts(issue_types: ['incident'])
+ result = resolve_issue_status_counts(types: ['incident'])
expect(result.all).to eq 1
expect(result.opened).to eq 0
expect(result.closed).to eq 1
end
- # The state param is ignored in IssuableFinder#count_by_state
- it 'ignores state filter', :aggregate_failures do
- result = resolve_issue_status_counts(state: 'closed')
-
- expect(result.all).to eq 2
- expect(result.opened).to eq 1
- expect(result.closed).to eq 1
- end
-
private
def resolve_issue_status_counts(args = {}, context = { current_user: current_user })
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 43cbd4d2bdd..f6f746a8572 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Resolvers::IssuesResolver do
include GraphqlHelpers
- let(:current_user) { create(:user) }
+ let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
@@ -25,7 +25,7 @@ RSpec.describe Resolvers::IssuesResolver do
end
context "with a project" do
- before do
+ before_all do
project.add_developer(current_user)
create(:label_link, label: label1, target: issue1)
create(:label_link, label: label1, target: issue2)
@@ -43,11 +43,11 @@ RSpec.describe Resolvers::IssuesResolver do
end
it 'filters by milestone' do
- expect(resolve_issues(milestone_title: milestone.title)).to contain_exactly(issue1)
+ expect(resolve_issues(milestone_title: [milestone.title])).to contain_exactly(issue1)
end
it 'filters by assignee_username' do
- expect(resolve_issues(assignee_username: assignee.username)).to contain_exactly(issue2)
+ expect(resolve_issues(assignee_username: [assignee.username])).to contain_exactly(issue2)
end
it 'filters by two assignees' do
@@ -112,15 +112,19 @@ RSpec.describe Resolvers::IssuesResolver do
describe 'filters by issue_type' do
it 'filters by a single type' do
- expect(resolve_issues(issue_types: ['incident'])).to contain_exactly(issue1)
+ expect(resolve_issues(types: %w[incident])).to contain_exactly(issue1)
+ end
+
+ it 'filters by a single type, negative assertion' do
+ expect(resolve_issues(types: %w[issue])).not_to include(issue1)
end
it 'filters by more than one type' do
- expect(resolve_issues(issue_types: %w(incident issue))).to contain_exactly(issue1, issue2)
+ expect(resolve_issues(types: %w[incident issue])).to contain_exactly(issue1, issue2)
end
it 'ignores the filter if none given' do
- expect(resolve_issues(issue_types: [])).to contain_exactly(issue1, issue2)
+ expect(resolve_issues(types: [])).to contain_exactly(issue1, issue2)
end
end
@@ -143,44 +147,44 @@ RSpec.describe Resolvers::IssuesResolver do
describe 'sorting' do
context 'when sorting by created' do
it 'sorts issues ascending' do
- expect(resolve_issues(sort: 'created_asc')).to eq [issue1, issue2]
+ expect(resolve_issues(sort: 'created_asc').to_a).to eq [issue1, issue2]
end
it 'sorts issues descending' do
- expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1]
+ expect(resolve_issues(sort: 'created_desc').to_a).to eq [issue2, issue1]
end
end
context 'when sorting by due date' do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :public) }
let_it_be(:due_issue1) { create(:issue, project: project, due_date: 3.days.from_now) }
let_it_be(:due_issue2) { create(:issue, project: project, due_date: nil) }
let_it_be(:due_issue3) { create(:issue, project: project, due_date: 2.days.ago) }
let_it_be(:due_issue4) { create(:issue, project: project, due_date: nil) }
it 'sorts issues ascending' do
- expect(resolve_issues(sort: :due_date_asc)).to eq [due_issue3, due_issue1, due_issue4, due_issue2]
+ expect(resolve_issues(sort: :due_date_asc).to_a).to eq [due_issue3, due_issue1, due_issue4, due_issue2]
end
it 'sorts issues descending' do
- expect(resolve_issues(sort: :due_date_desc)).to eq [due_issue1, due_issue3, due_issue4, due_issue2]
+ expect(resolve_issues(sort: :due_date_desc).to_a).to eq [due_issue1, due_issue3, due_issue4, due_issue2]
end
end
context 'when sorting by relative position' do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :public) }
let_it_be(:relative_issue1) { create(:issue, project: project, relative_position: 2000) }
let_it_be(:relative_issue2) { create(:issue, project: project, relative_position: nil) }
let_it_be(:relative_issue3) { create(:issue, project: project, relative_position: 1000) }
let_it_be(:relative_issue4) { create(:issue, project: project, relative_position: nil) }
it 'sorts issues ascending' do
- expect(resolve_issues(sort: :relative_position_asc)).to eq [relative_issue3, relative_issue1, relative_issue4, relative_issue2]
+ expect(resolve_issues(sort: :relative_position_asc).to_a).to eq [relative_issue3, relative_issue1, relative_issue4, relative_issue2]
end
end
context 'when sorting by priority' do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :public) }
let_it_be(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) }
let_it_be(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) }
let_it_be(:priority_label1) { create(:label, project: project, priority: 1) }
@@ -200,7 +204,7 @@ RSpec.describe Resolvers::IssuesResolver do
end
context 'when sorting by label priority' do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :public) }
let_it_be(:label1) { create(:label, project: project, priority: 1) }
let_it_be(:label2) { create(:label, project: project, priority: 5) }
let_it_be(:label3) { create(:label, project: project, priority: 10) }
@@ -219,7 +223,7 @@ RSpec.describe Resolvers::IssuesResolver do
end
context 'when sorting by milestone due date' do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :public) }
let_it_be(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) }
let_it_be(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) }
let_it_be(:milestone_issue1) { create(:issue, project: project) }
@@ -236,17 +240,17 @@ RSpec.describe Resolvers::IssuesResolver do
end
context 'when sorting by severity' do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :public) }
let_it_be(:issue_high_severity) { create_issue_with_severity(project, severity: :high) }
let_it_be(:issue_low_severity) { create_issue_with_severity(project, severity: :low) }
let_it_be(:issue_no_severity) { create(:incident, project: project) }
it 'sorts issues ascending' do
- expect(resolve_issues(sort: :severity_asc)).to eq([issue_no_severity, issue_low_severity, issue_high_severity])
+ expect(resolve_issues(sort: :severity_asc).to_a).to eq([issue_no_severity, issue_low_severity, issue_high_severity])
end
it 'sorts issues descending' do
- expect(resolve_issues(sort: :severity_desc)).to eq([issue_high_severity, issue_low_severity, issue_no_severity])
+ expect(resolve_issues(sort: :severity_desc).to_a).to eq([issue_high_severity, issue_low_severity, issue_no_severity])
end
end
end
@@ -267,7 +271,9 @@ RSpec.describe Resolvers::IssuesResolver do
it 'batches queries that only include IIDs', :request_store do
result = batch_sync(max_queries: 2) do
- resolve_issues(iid: issue1.iid) + resolve_issues(iids: issue2.iid)
+ [issue1, issue2]
+ .map { |issue| resolve_issues(iid: issue.iid.to_s) }
+ .flat_map(&:to_a)
end
expect(result).to contain_exactly(issue1, issue2)
diff --git a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
index deb5ff584cf..84ef906b72f 100644
--- a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe Resolvers::MergeRequestPipelinesResolver do
end
def resolve_pipelines
- resolve(described_class, obj: merge_request, ctx: { current_user: current_user })
+ sync(resolve(described_class, obj: merge_request, ctx: { current_user: current_user }))
end
it 'resolves only MRs for the passed merge request' do
diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
index 3a3393a185c..63fbd04848d 100644
--- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
@@ -75,9 +75,9 @@ RSpec.describe Resolvers::MergeRequestsResolver do
it 'can batch-resolve merge requests from different projects' do
# 2 queries for project_authorizations, and 2 for merge_requests
result = batch_sync(max_queries: queries_per_project * 2) do
- resolve_mr(project, iids: iid_1) +
- resolve_mr(project, iids: iid_2) +
- resolve_mr(other_project, iids: other_iid)
+ resolve_mr(project, iids: [iid_1]) +
+ resolve_mr(project, iids: [iid_2]) +
+ resolve_mr(other_project, iids: [other_iid])
end
expect(result).to contain_exactly(merge_request_1, merge_request_2, other_merge_request)
@@ -110,7 +110,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do
context 'by source branches' do
it 'takes one argument' do
- result = resolve_mr(project, source_branch: [merge_request_3.source_branch])
+ result = resolve_mr(project, source_branches: [merge_request_3.source_branch])
expect(result).to contain_exactly(merge_request_3)
end
@@ -118,7 +118,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do
it 'takes more than one argument' do
mrs = [merge_request_3, merge_request_4]
branches = mrs.map(&:source_branch)
- result = resolve_mr(project, source_branch: branches )
+ result = resolve_mr(project, source_branches: branches )
expect(result).to match_array(mrs)
end
@@ -126,7 +126,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do
context 'by target branches' do
it 'takes one argument' do
- result = resolve_mr(project, target_branch: [merge_request_3.target_branch])
+ result = resolve_mr(project, target_branches: [merge_request_3.target_branch])
expect(result).to contain_exactly(merge_request_3)
end
@@ -134,7 +134,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do
it 'takes more than one argument' do
mrs = [merge_request_3, merge_request_4]
branches = mrs.map(&:target_branch)
- result = resolve_mr(project, target_branch: branches )
+ result = resolve_mr(project, target_branches: branches )
expect(result.compact).to match_array(mrs)
end
@@ -153,13 +153,13 @@ RSpec.describe Resolvers::MergeRequestsResolver do
let_it_be(:with_label) { create(:labeled_merge_request, :closed, labels: [label], **common_attrs) }
it 'takes one argument' do
- result = resolve_mr(project, label_name: [label.title])
+ result = resolve_mr(project, labels: [label.title])
expect(result).to contain_exactly(merge_request_6, with_label)
end
it 'takes multiple arguments, with semantics of ALL MUST MATCH' do
- result = resolve_mr(project, label_name: merge_request_6.labels.map(&:title))
+ result = resolve_mr(project, labels: merge_request_6.labels.map(&:title))
expect(result).to contain_exactly(merge_request_6)
end
@@ -201,7 +201,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do
it 'requires all filters' do
create(:merge_request, :closed, source_project: project, target_project: project, source_branch: merge_request_4.source_branch)
- result = resolve_mr(project, source_branch: [merge_request_4.source_branch], state: 'locked')
+ result = resolve_mr(project, source_branches: [merge_request_4.source_branch], state: 'locked')
expect(result.compact).to contain_exactly(merge_request_4)
end
@@ -236,7 +236,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do
end
def resolve_mr_single(project, iid)
- resolve_mr(project, resolver: described_class.single, iids: iid)
+ resolve_mr(project, resolver: described_class.single, iid: iid.to_s)
end
def resolve_mr(project, resolver: described_class, user: current_user, **args)
diff --git a/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb b/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb
index bfb3ce91d58..45777aa96e1 100644
--- a/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb
@@ -8,14 +8,16 @@ RSpec.describe Resolvers::ProjectMergeRequestsResolver do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:current_user) { create(:user) }
let_it_be(:other_user) { create(:user) }
+ let_it_be(:reviewer) { create(:user) }
- let_it_be(:merge_request_with_author_and_assignee) do
+ let_it_be(:merge_request) do
create(:merge_request,
:unique_branches,
source_project: project,
target_project: project,
author: other_user,
- assignee: other_user)
+ assignee: other_user,
+ reviewers: [reviewer])
end
before do
@@ -26,7 +28,7 @@ RSpec.describe Resolvers::ProjectMergeRequestsResolver do
it 'filters merge requests by assignee username' do
result = resolve_mr(project, assignee_username: other_user.username)
- expect(result).to eq([merge_request_with_author_and_assignee])
+ expect(result).to contain_exactly(merge_request)
end
it 'does not find anything' do
@@ -40,7 +42,7 @@ RSpec.describe Resolvers::ProjectMergeRequestsResolver do
it 'filters merge requests by author username' do
result = resolve_mr(project, author_username: other_user.username)
- expect(result).to eq([merge_request_with_author_and_assignee])
+ expect(result).to contain_exactly(merge_request)
end
it 'does not find anything' do
@@ -50,7 +52,21 @@ RSpec.describe Resolvers::ProjectMergeRequestsResolver do
end
end
- def resolve_mr(project, args, resolver: described_class, user: current_user)
+ context 'by reviewer' do
+ it 'filters merge requests by reviewer username' do
+ result = resolve_mr(project, reviewer_username: reviewer.username)
+
+ expect(result).to contain_exactly(merge_request)
+ end
+
+ it 'does not find anything' do
+ result = resolve_mr(project, reviewer_username: 'unknown-user')
+
+ expect(result).to be_empty
+ end
+ end
+
+ def resolve_mr(project, resolver: described_class, user: current_user, **args)
resolve(resolver, obj: project, args: args, ctx: { current_user: user })
end
end
diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
index 1950c2ca067..b852b349d4f 100644
--- a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
@@ -18,6 +18,10 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
resolve(described_class, obj: project, args: args, ctx: { current_user: current_user })
end
+ before do
+ project.add_developer(current_user)
+ end
+
it 'resolves pipeline for the passed iid' do
result = batch_sync do
resolve_pipeline(project, { iid: '1234' })
@@ -26,6 +30,21 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
expect(result).to eq(pipeline)
end
+ it 'keeps the queries under the threshold' do
+ create(:ci_pipeline, project: project, iid: '1235')
+
+ control = ActiveRecord::QueryRecorder.new do
+ batch_sync { resolve_pipeline(project, { iid: '1234' }) }
+ end
+
+ expect do
+ batch_sync do
+ resolve_pipeline(project, { iid: '1234' })
+ resolve_pipeline(project, { iid: '1235' })
+ end
+ end.not_to exceed_query_limit(control)
+ end
+
it 'does not resolve a pipeline outside the project' do
result = batch_sync do
resolve_pipeline(other_pipeline.project, { iid: '1234' })
diff --git a/spec/graphql/resolvers/project_pipeline_statistics_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_statistics_resolver_spec.rb
new file mode 100644
index 00000000000..c0367f7d42e
--- /dev/null
+++ b/spec/graphql/resolvers/project_pipeline_statistics_resolver_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+
+ specify do
+ expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType)
+ end
+
+ def resolve_statistics(project, args)
+ resolve(described_class, obj: project, args: args)
+ end
+
+ describe '#resolve' do
+ it 'returns the pipelines statistics for a given project' do
+ result = resolve_statistics(project, {})
+ expect(result.keys).to contain_exactly(
+ :week_pipelines_labels,
+ :week_pipelines_totals,
+ :week_pipelines_successful,
+ :month_pipelines_labels,
+ :month_pipelines_totals,
+ :month_pipelines_successful,
+ :year_pipelines_labels,
+ :year_pipelines_totals,
+ :year_pipelines_successful,
+ :pipeline_times_labels,
+ :pipeline_times_values
+ )
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
deleted file mode 100644
index ad59cb6b95e..00000000000
--- a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Resolvers::Projects::JiraImportsResolver do
- include GraphqlHelpers
-
- specify do
- expect(described_class).to have_nullable_graphql_type(Types::JiraImportType.connection_type)
- end
-
- describe '#resolve' do
- let_it_be(:user) { create(:user) }
- let_it_be(:project, reload: true) { create(:project, :public) }
-
- context 'when project does not have Jira imports' do
- let(:current_user) { user }
-
- context 'when user cannot read Jira imports' do
- context 'when anonymous user' do
- let(:current_user) { nil }
-
- it_behaves_like 'no Jira import access'
- end
- end
-
- context 'when user can read Jira import data' do
- before do
- project.add_guest(user)
- end
-
- it_behaves_like 'no Jira import data present'
-
- it 'does not raise access error' do
- expect do
- resolve_imports
- end.not_to raise_error
- end
- end
- end
-
- context 'when project has Jira imports' do
- let_it_be(:current_user) { user }
- let_it_be(:jira_import1) { create(:jira_import_state, :finished, project: project, jira_project_key: 'AA', created_at: 2.days.ago) }
- let_it_be(:jira_import2) { create(:jira_import_state, :finished, project: project, jira_project_key: 'BB', created_at: 5.days.ago) }
-
- context 'when user cannot read Jira imports' do
- context 'when anonymous user' do
- let(:current_user) { nil }
-
- it_behaves_like 'no Jira import access'
- end
- end
-
- context 'when user can access Jira imports' do
- before do
- project.add_guest(user)
- end
-
- it 'returns Jira imports sorted ascending by created_at time' do
- imports = resolve_imports
-
- expect(imports.size).to eq 2
- expect(imports.map(&:jira_project_key)).to eq %w(BB AA)
- end
- end
- end
- end
-
- def resolve_imports(args = {}, context = { current_user: current_user })
- resolve(described_class, obj: project, args: args, ctx: context)
- end
-end
diff --git a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
index 6f7feff8fe5..2d8929c0e8f 100644
--- a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
@@ -6,16 +6,18 @@ RSpec.describe Resolvers::Projects::SnippetsResolver do
include GraphqlHelpers
describe '#resolve' do
- let_it_be(:current_user) { create(:user) }
+ let_it_be(:user) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:project) { create(:project) }
- let_it_be(:personal_snippet) { create(:personal_snippet, :private, author: current_user) }
- let_it_be(:project_snippet) { create(:project_snippet, :internal, author: current_user, project: project) }
+ let_it_be(:personal_snippet) { create(:personal_snippet, :private, author: user) }
+ let_it_be(:project_snippet) { create(:project_snippet, :internal, author: user, project: project) }
let_it_be(:other_project_snippet) { create(:project_snippet, :public, author: other_user, project: project) }
- before do
- project.add_developer(current_user)
+ let(:current_user) { user }
+
+ before_all do
+ project.add_developer(user)
end
it 'calls SnippetsFinder' do
@@ -42,20 +44,26 @@ RSpec.describe Resolvers::Projects::SnippetsResolver do
end
it 'returns the snippets by gid' do
- snippets = resolve_snippets(args: { ids: project_snippet.to_global_id })
+ snippets = resolve_snippets(args: { ids: [global_id_of(project_snippet)] })
expect(snippets).to contain_exactly(project_snippet)
end
it 'returns the snippets by array of gid' do
args = {
- ids: [project_snippet.to_global_id, other_project_snippet.to_global_id]
+ ids: [global_id_of(project_snippet), global_id_of(other_project_snippet)]
}
snippets = resolve_snippets(args: args)
expect(snippets).to contain_exactly(project_snippet, other_project_snippet)
end
+
+ it 'returns an error if the gid is invalid' do
+ expect do
+ resolve_snippets(args: { ids: ['foo'] })
+ end.to raise_error(GraphQL::CoercionError)
+ end
end
context 'when no project is provided' do
@@ -65,8 +73,10 @@ RSpec.describe Resolvers::Projects::SnippetsResolver do
end
context 'when provided user is not current user' do
+ let(:current_user) { other_user }
+
it 'returns no snippets' do
- expect(resolve_snippets(context: { current_user: other_user }, args: { ids: project_snippet.to_global_id })).to be_empty
+ expect(resolve_snippets(args: { ids: [global_id_of(project_snippet)] })).to be_empty
end
end
diff --git a/spec/graphql/resolvers/releases_resolver_spec.rb b/spec/graphql/resolvers/releases_resolver_spec.rb
index b9b90686aa7..89623be891f 100644
--- a/spec/graphql/resolvers/releases_resolver_spec.rb
+++ b/spec/graphql/resolvers/releases_resolver_spec.rb
@@ -17,6 +17,7 @@ RSpec.describe Resolvers::ReleasesResolver do
let_it_be(:public_user) { create(:user) }
let(:args) { { sort: :released_at_desc } }
+ let(:all_releases) { [release_v1, release_v2, release_v3] }
before do
project.add_developer(developer)
@@ -27,7 +28,7 @@ RSpec.describe Resolvers::ReleasesResolver do
let(:current_user) { public_user }
it 'returns an empty array' do
- expect(resolve_releases).to eq([])
+ expect(resolve_releases).to be_empty
end
end
@@ -35,7 +36,7 @@ RSpec.describe Resolvers::ReleasesResolver do
let(:current_user) { developer }
it 'returns all releases associated to the project' do
- expect(resolve_releases).to eq([release_v3, release_v2, release_v1])
+ expect(resolve_releases).to match_array(all_releases)
end
describe 'sorting behavior' do
@@ -43,7 +44,9 @@ RSpec.describe Resolvers::ReleasesResolver do
let(:args) { { sort: :released_at_desc } }
it 'returns the releases ordered by released_at in descending order' do
- expect(resolve_releases).to eq([release_v3, release_v2, release_v1])
+ expect(resolve_releases.to_a)
+ .to match_array(all_releases)
+ .and be_sorted(:released_at, :desc)
end
end
@@ -51,7 +54,9 @@ RSpec.describe Resolvers::ReleasesResolver do
let(:args) { { sort: :released_at_asc } }
it 'returns the releases ordered by released_at in ascending order' do
- expect(resolve_releases).to eq([release_v1, release_v2, release_v3])
+ expect(resolve_releases.to_a)
+ .to match_array(all_releases)
+ .and be_sorted(:released_at, :asc)
end
end
@@ -59,7 +64,9 @@ RSpec.describe Resolvers::ReleasesResolver do
let(:args) { { sort: :created_desc } }
it 'returns the releases ordered by created_at in descending order' do
- expect(resolve_releases).to eq([release_v1, release_v3, release_v2])
+ expect(resolve_releases.to_a)
+ .to match_array(all_releases)
+ .and be_sorted(:created_at, :desc)
end
end
@@ -67,7 +74,9 @@ RSpec.describe Resolvers::ReleasesResolver do
let(:args) { { sort: :created_asc } }
it 'returns the releases ordered by created_at in ascending order' do
- expect(resolve_releases).to eq([release_v2, release_v3, release_v1])
+ expect(resolve_releases.to_a)
+ .to match_array(all_releases)
+ .and be_sorted(:created_at, :asc)
end
end
end
diff --git a/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb b/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb
index 16e69f662c0..ebe286769cf 100644
--- a/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb
+++ b/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb
@@ -16,16 +16,18 @@ RSpec.describe Resolvers::Snippets::BlobsResolver do
context 'when user is not authorized' do
let(:other_user) { create(:user) }
- it 'raises an error' do
- expect do
- resolve_blobs(snippet, user: other_user)
- end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ it 'redacts the field' do
+ expect(resolve_blobs(snippet, user: other_user)).to be_nil
end
end
context 'when using no filter' do
it 'returns all snippet blobs' do
- expect(resolve_blobs(snippet).map(&:path)).to contain_exactly(*snippet.list_files)
+ result = resolve_blobs(snippet, args: {})
+
+ expect(result).to match_array(snippet.list_files.map do |file|
+ have_attributes(path: file)
+ end)
end
end
@@ -34,7 +36,13 @@ RSpec.describe Resolvers::Snippets::BlobsResolver do
it 'returns an array of files' do
path = 'CHANGELOG'
- expect(resolve_blobs(snippet, args: { paths: path }).first.path).to eq(path)
+ expect(resolve_blobs(snippet, paths: [path])).to contain_exactly(have_attributes(path: path))
+ end
+ end
+
+ context 'the argument does not match anything' do
+ it 'returns an empty result' do
+ expect(resolve_blobs(snippet, paths: ['does not exist'])).to be_empty
end
end
@@ -42,13 +50,15 @@ RSpec.describe Resolvers::Snippets::BlobsResolver do
it 'returns an array of files' do
paths = ['CHANGELOG', 'README.md']
- expect(resolve_blobs(snippet, args: { paths: paths }).map(&:path)).to contain_exactly(*paths)
+ expect(resolve_blobs(snippet, paths: paths)).to match_array(paths.map do |file|
+ have_attributes(path: file)
+ end)
end
end
end
end
- def resolve_blobs(snippet, user: current_user, args: {})
+ def resolve_blobs(snippet, user: current_user, paths: [], args: { paths: paths })
resolve(described_class, args: args, ctx: { current_user: user }, obj: snippet)
end
end
diff --git a/spec/graphql/resolvers/snippets_resolver_spec.rb b/spec/graphql/resolvers/snippets_resolver_spec.rb
index a58d9c5ac3a..11cb1c0ec4b 100644
--- a/spec/graphql/resolvers/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/snippets_resolver_spec.rb
@@ -84,19 +84,18 @@ RSpec.describe Resolvers::SnippetsResolver do
end
it 'returns the snippets by single gid' do
- snippets = resolve_snippets(args: { ids: personal_snippet.to_global_id })
+ snippets = resolve_snippets(args: { ids: [global_id_of(personal_snippet)] })
expect(snippets).to contain_exactly(personal_snippet)
end
it 'returns the snippets by array of gid' do
- args = {
- ids: [personal_snippet.to_global_id, project_snippet.to_global_id]
- }
+ snippets = [personal_snippet, project_snippet]
+ args = { ids: snippets.map { |s| global_id_of(s) } }
- snippets = resolve_snippets(args: args)
+ found = resolve_snippets(args: args)
- expect(snippets).to contain_exactly(personal_snippet, project_snippet)
+ expect(found).to match_array(snippets)
end
it 'returns an error if the id cannot be coerced' do
diff --git a/spec/graphql/resolvers/todo_resolver_spec.rb b/spec/graphql/resolvers/todo_resolver_spec.rb
index c764f389c16..ac14852b365 100644
--- a/spec/graphql/resolvers/todo_resolver_spec.rb
+++ b/spec/graphql/resolvers/todo_resolver_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Resolvers::TodoResolver do
it 'calls TodosFinder' do
expect_next_instance_of(TodosFinder) do |finder|
- expect(finder).to receive(:execute)
+ expect(finder).to receive(:execute).and_call_original
end
resolve_todos
@@ -48,7 +48,7 @@ RSpec.describe Resolvers::TodoResolver do
end
it 'returns the todos for single filter' do
- todos = resolve_todos(type: 'MergeRequest')
+ todos = resolve_todos(type: ['MergeRequest'])
expect(todos).to contain_exactly(merge_request_todo_pending)
end
diff --git a/spec/graphql/resolvers/user_discussions_count_resolver_spec.rb b/spec/graphql/resolvers/user_discussions_count_resolver_spec.rb
new file mode 100644
index 00000000000..cc855bbcb53
--- /dev/null
+++ b/spec/graphql/resolvers/user_discussions_count_resolver_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::UserDiscussionsCountResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:private_project) { create(:project, :private) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:private_issue) { create(:issue, project: private_project) }
+ let_it_be(:public_discussions) { create_list(:discussion_note_on_issue, 2, noteable: issue, project: project) }
+ let_it_be(:system_discussion) { create(:discussion_note_on_issue, system: true, noteable: issue, project: project) }
+ let_it_be(:private_discussion) { create_list(:discussion_note_on_issue, 3, noteable: private_issue, project: private_project) }
+
+ specify do
+ expect(described_class).to have_nullable_graphql_type(GraphQL::INT_TYPE)
+ end
+
+ context 'when counting discussions from a public issue' do
+ subject { batch_sync { resolve_user_discussions_count(issue) } }
+
+ it 'returns the number of discussions for the issue' do
+ expect(subject).to eq(2)
+ end
+ end
+
+ context 'when a user has permission to view discussions' do
+ before do
+ private_project.add_developer(user)
+ end
+
+ subject { batch_sync { resolve_user_discussions_count(private_issue) } }
+
+ it 'returns the number of non-system discussions for the issue' do
+ expect(subject).to eq(3)
+ end
+ end
+
+ context 'when a user does not have permission to view discussions' do
+ subject { batch_sync { resolve_user_discussions_count(private_issue) } }
+
+ it 'returns no discussions' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+ end
+
+ def resolve_user_discussions_count(obj)
+ resolve(described_class, obj: obj, ctx: { current_user: user })
+ end
+end
diff --git a/spec/graphql/resolvers/user_notes_count_resolver_spec.rb b/spec/graphql/resolvers/user_notes_count_resolver_spec.rb
new file mode 100644
index 00000000000..3cb0810c698
--- /dev/null
+++ b/spec/graphql/resolvers/user_notes_count_resolver_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::UserNotesCountResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:private_project) { create(:project, :repository, :private) }
+
+ specify do
+ expect(described_class).to have_nullable_graphql_type(GraphQL::INT_TYPE)
+ end
+
+ context 'when counting notes from an issue' do
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:private_issue) { create(:issue, project: private_project) }
+ let_it_be(:public_notes) { create_list(:note, 2, noteable: issue, project: project) }
+ let_it_be(:system_note) { create(:note, system: true, noteable: issue, project: project) }
+ let_it_be(:private_notes) { create_list(:note, 3, noteable: private_issue, project: private_project) }
+
+ context 'when counting notes from a public issue' do
+ subject { batch_sync { resolve_user_notes_count(issue) } }
+
+ it 'returns the number of non-system notes for the issue' do
+ expect(subject).to eq(2)
+ end
+ end
+
+ context 'when a user has permission to view notes' do
+ before do
+ private_project.add_developer(user)
+ end
+
+ subject { batch_sync { resolve_user_notes_count(private_issue) } }
+
+ it 'returns the number of notes for the issue' do
+ expect(subject).to eq(3)
+ end
+ end
+
+ context 'when a user does not have permission to view notes' do
+ subject { batch_sync { resolve_user_notes_count(private_issue) } }
+
+ it 'returns no notes' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+ end
+
+ context 'when counting notes from a merge request' do
+ let_it_be(:merge_request) { create(:merge_request, source_project: project) }
+ let_it_be(:private_merge_request) { create(:merge_request, source_project: private_project) }
+ let_it_be(:public_notes) { create_list(:note, 2, noteable: merge_request, project: project) }
+ let_it_be(:system_note) { create(:note, system: true, noteable: merge_request, project: project) }
+ let_it_be(:private_notes) { create_list(:note, 3, noteable: private_merge_request, project: private_project) }
+
+ context 'when counting notes from a public merge request' do
+ subject { batch_sync { resolve_user_notes_count(merge_request) } }
+
+ it 'returns the number of non-system notes for the merge request' do
+ expect(subject).to eq(2)
+ end
+ end
+
+ context 'when a user has permission to view notes' do
+ before do
+ private_project.add_developer(user)
+ end
+
+ subject { batch_sync { resolve_user_notes_count(private_merge_request) } }
+
+ it 'returns the number of notes for the merge request' do
+ expect(subject).to eq(3)
+ end
+ end
+
+ context 'when a user does not have permission to view notes' do
+ subject { batch_sync { resolve_user_notes_count(private_merge_request) } }
+
+ it 'returns no notes' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+ end
+ end
+
+ def resolve_user_notes_count(obj)
+ resolve(described_class, obj: obj, ctx: { current_user: user })
+ end
+end
diff --git a/spec/graphql/resolvers/users/snippets_resolver_spec.rb b/spec/graphql/resolvers/users/snippets_resolver_spec.rb
index 9ccbebc59e6..11a5b7517e0 100644
--- a/spec/graphql/resolvers/users/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/users/snippets_resolver_spec.rb
@@ -51,24 +51,23 @@ RSpec.describe Resolvers::Users::SnippetsResolver do
end
it 'returns the snippets by single gid' do
- snippets = resolve_snippets(args: { ids: private_personal_snippet.to_global_id })
+ snippets = resolve_snippets(args: { ids: [global_id_of(private_personal_snippet)] })
expect(snippets).to contain_exactly(private_personal_snippet)
end
it 'returns the snippets by array of gid' do
- args = {
- ids: [private_personal_snippet.to_global_id, public_personal_snippet.to_global_id]
- }
+ snippets = [private_personal_snippet, public_personal_snippet]
+ args = { ids: snippets.map { |s| global_id_of(s) } }
- snippets = resolve_snippets(args: args)
+ found = resolve_snippets(args: args)
- expect(snippets).to contain_exactly(private_personal_snippet, public_personal_snippet)
+ expect(found).to match_array(snippets)
end
it 'returns an error if the gid is invalid' do
args = {
- ids: [private_personal_snippet.to_global_id, 'foo']
+ ids: [global_id_of(private_personal_snippet), 'foo']
}
expect do
diff --git a/spec/graphql/types/alert_management/domain_filter_enum_spec.rb b/spec/graphql/types/alert_management/domain_filter_enum_spec.rb
new file mode 100644
index 00000000000..2111a33b8b4
--- /dev/null
+++ b/spec/graphql/types/alert_management/domain_filter_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AlertManagementDomainFilter'] do
+ specify { expect(described_class.graphql_name).to eq('AlertManagementDomainFilter') }
+
+ it 'exposes all the severity values' do
+ expect(described_class.values.keys).to include(*%w[threat_monitoring operations])
+ end
+end
diff --git a/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb b/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb
index 0e9994035d8..b10c2a2ab2a 100644
--- a/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb
+++ b/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb
@@ -11,11 +11,14 @@ RSpec.describe GitlabSchema.types['AlertManagementPrometheusIntegration'] do
describe 'resolvers' do
shared_examples_for 'has field with value' do |field_name|
it 'correctly renders the field' do
- expect(resolve_field(field_name, integration)).to eq(value)
+ result = resolve_field(field_name, integration, current_user: user)
+
+ expect(result).to eq(value)
end
end
let_it_be_with_reload(:integration) { create(:prometheus_service) }
+ let_it_be(:user) { create(:user, maintainer_projects: [integration.project]) }
it_behaves_like 'has field with value', 'name' do
let(:value) { integration.title }
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index d61ea6aa6e9..54b59317b55 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -145,11 +145,11 @@ RSpec.describe Types::BaseField do
describe '#description' do
context 'feature flag given' do
- let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false, description: 'Test description') }
+ let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false, description: 'Test description.') }
let(:flag) { :test_flag }
it 'prepends the description' do
- expect(field.description). to eq 'Test description. Available only when feature flag `test_flag` is enabled'
+ expect(field.description). to eq 'Test description. Available only when feature flag `test_flag` is enabled.'
end
context 'falsey feature_flag values' do
@@ -164,7 +164,7 @@ RSpec.describe Types::BaseField do
with_them do
it 'returns the correct description' do
- expect(field.description).to eq('Test description')
+ expect(field.description).to eq('Test description.')
end
end
end
@@ -181,11 +181,11 @@ RSpec.describe Types::BaseField do
it 'interacts well with the `feature_flag` property' do
field = subject(
deprecated: { milestone: '1.10', reason: 'Deprecation reason' },
- description: 'Field description',
+ description: 'Field description.',
feature_flag: 'foo_flag'
)
- expectation = 'Field description. Available only when feature flag `foo_flag` is enabled. Deprecated in 1.10: Deprecation reason'
+ expectation = 'Field description. Available only when feature flag `foo_flag` is enabled. Deprecated in 1.10: Deprecation reason.'
expect(field.description).to eq(expectation)
end
diff --git a/spec/graphql/types/ci/analytics_type_spec.rb b/spec/graphql/types/ci/analytics_type_spec.rb
new file mode 100644
index 00000000000..c8462d40769
--- /dev/null
+++ b/spec/graphql/types/ci/analytics_type_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::AnalyticsType do
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ weekPipelinesTotals
+ weekPipelinesLabels
+ weekPipelinesSuccessful
+ monthPipelinesLabels
+ monthPipelinesTotals
+ monthPipelinesSuccessful
+ yearPipelinesLabels
+ yearPipelinesTotals
+ yearPipelinesSuccessful
+ pipelineTimesLabels
+ pipelineTimesValues
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/ci/config/config_type_spec.rb b/spec/graphql/types/ci/config/config_type_spec.rb
new file mode 100644
index 00000000000..edd190a4365
--- /dev/null
+++ b/spec/graphql/types/ci/config/config_type_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::Config::ConfigType do
+ specify { expect(described_class.graphql_name).to eq('CiConfig') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ errors
+ mergedYaml
+ stages
+ status
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/ci/config/group_type_spec.rb b/spec/graphql/types/ci/config/group_type_spec.rb
new file mode 100644
index 00000000000..7d808e85371
--- /dev/null
+++ b/spec/graphql/types/ci/config/group_type_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::Config::GroupType do
+ specify { expect(described_class.graphql_name).to eq('CiConfigGroup') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ name
+ jobs
+ size
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/ci/config/job_type_spec.rb b/spec/graphql/types/ci/config/job_type_spec.rb
new file mode 100644
index 00000000000..600d665a84b
--- /dev/null
+++ b/spec/graphql/types/ci/config/job_type_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::Config::JobType do
+ specify { expect(described_class.graphql_name).to eq('CiConfigJob') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ name
+ group_name
+ stage
+ needs
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/ci/config/need_type_spec.rb b/spec/graphql/types/ci/config/need_type_spec.rb
new file mode 100644
index 00000000000..3387049a81d
--- /dev/null
+++ b/spec/graphql/types/ci/config/need_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::Config::NeedType do
+ specify { expect(described_class.graphql_name).to eq('CiConfigNeed') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ name
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/ci/config/stage_type_spec.rb b/spec/graphql/types/ci/config/stage_type_spec.rb
new file mode 100644
index 00000000000..aba97f8c7ed
--- /dev/null
+++ b/spec/graphql/types/ci/config/stage_type_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::Config::StageType do
+ specify { expect(described_class.graphql_name).to eq('CiConfigStage') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ name
+ groups
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/ci/job_artifact_file_type_enum_spec.rb b/spec/graphql/types/ci/job_artifact_file_type_enum_spec.rb
new file mode 100644
index 00000000000..78a32595730
--- /dev/null
+++ b/spec/graphql/types/ci/job_artifact_file_type_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['JobArtifactFileType'] do
+ it 'exposes all job artifact file types' do
+ expect(described_class.values.keys).to contain_exactly(
+ *::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.keys.map(&:to_s).map(&:upcase)
+ )
+ end
+end
diff --git a/spec/graphql/types/ci/job_artifact_type_spec.rb b/spec/graphql/types/ci/job_artifact_type_spec.rb
new file mode 100644
index 00000000000..d4dc5ef214d
--- /dev/null
+++ b/spec/graphql/types/ci/job_artifact_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['CiJobArtifact'] do
+ it 'has the correct fields' do
+ expected_fields = [:download_path, :file_type]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb
index 3dcb81eefbf..441a719df8c 100644
--- a/spec/graphql/types/ci/job_type_spec.rb
+++ b/spec/graphql/types/ci/job_type_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe Types::Ci::JobType do
needs
detailedStatus
scheduledAt
+ artifacts
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb
index f13f1c9afb2..d435e337ad7 100644
--- a/spec/graphql/types/ci/pipeline_type_spec.rb
+++ b/spec/graphql/types/ci/pipeline_type_spec.rb
@@ -6,4 +6,19 @@ RSpec.describe Types::Ci::PipelineType do
specify { expect(described_class.graphql_name).to eq('Pipeline') }
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Ci::Pipeline) }
+
+ it 'contains attributes related to a pipeline' do
+ expected_fields = %w[
+ id iid sha before_sha status detailed_status config_source duration
+ coverage created_at updated_at started_at finished_at committed_at
+ stages user retryable cancelable jobs source_job downstream
+ upstream path project active user_permissions
+ ]
+
+ if Gitlab.ee?
+ expected_fields << 'security_report_summary'
+ end
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
end
diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb
index e9bc7f6bb94..b43693e5804 100644
--- a/spec/graphql/types/commit_type_spec.rb
+++ b/spec/graphql/types/commit_type_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe GitlabSchema.types['Commit'] do
it 'contains attributes related to commit' do
expect(described_class).to have_graphql_fields(
- :id, :sha, :title, :description, :description_html, :message, :title_html, :authored_date,
+ :id, :sha, :short_id, :title, :description, :description_html, :message, :title_html, :authored_date,
:author_name, :author_gravatar, :author, :web_url, :web_path,
:pipelines, :signature_html
)
diff --git a/spec/graphql/types/container_repository_details_type_spec.rb b/spec/graphql/types/container_repository_details_type_spec.rb
index b5ff460fcf7..45f6449d8c8 100644
--- a/spec/graphql/types/container_repository_details_type_spec.rb
+++ b/spec/graphql/types/container_repository_details_type_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['ContainerRepositoryDetails'] do
- fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status tags]
+ fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status tags project]
it { expect(described_class.graphql_name).to eq('ContainerRepositoryDetails') }
diff --git a/spec/graphql/types/container_repository_type_spec.rb b/spec/graphql/types/container_repository_type_spec.rb
index 3d3445ba5c3..87e1c11ce19 100644
--- a/spec/graphql/types/container_repository_type_spec.rb
+++ b/spec/graphql/types/container_repository_type_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['ContainerRepository'] do
- fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status]
+ fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status project]
it { expect(described_class.graphql_name).to eq('ContainerRepository') }
diff --git a/spec/graphql/types/countable_connection_type_spec.rb b/spec/graphql/types/countable_connection_type_spec.rb
index 3b3c02baa5d..648dbacff42 100644
--- a/spec/graphql/types/countable_connection_type_spec.rb
+++ b/spec/graphql/types/countable_connection_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe GitlabSchema.types['MergeRequestConnection'] do
+RSpec.describe GitlabSchema.types['PipelineConnection'] do
it 'has the expected fields' do
expected_fields = %i[count page_info edges nodes]
diff --git a/spec/graphql/types/group_member_relation_enum_spec.rb b/spec/graphql/types/group_member_relation_enum_spec.rb
new file mode 100644
index 00000000000..315809ef75e
--- /dev/null
+++ b/spec/graphql/types/group_member_relation_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::GroupMemberRelationEnum do
+ specify { expect(described_class.graphql_name).to eq('GroupMemberRelation') }
+
+ it 'exposes all the existing group member relation type values' do
+ expect(described_class.values.keys).to contain_exactly('DIRECT', 'INHERITED', 'DESCENDANTS')
+ end
+end
diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb
index 7d14ef87551..de19e8b602a 100644
--- a/spec/graphql/types/group_type_spec.rb
+++ b/spec/graphql/types/group_type_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe GitlabSchema.types['Group'] do
subgroup_creation_level require_two_factor_authentication
two_factor_grace_period auto_devops_enabled emails_disabled
mentions_disabled parent boards milestones group_members
- merge_requests
+ merge_requests container_repositories container_repositories_count
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/merge_request_connection_type_spec.rb b/spec/graphql/types/merge_request_connection_type_spec.rb
new file mode 100644
index 00000000000..f4ab6c79721
--- /dev/null
+++ b/spec/graphql/types/merge_request_connection_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['MergeRequestConnection'] do
+ it 'has the expected fields' do
+ expected_fields = %i[count totalTimeToMerge page_info edges nodes]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index 8800250b103..51e7b4029d5 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -27,18 +27,27 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
upvotes downvotes head_pipeline pipelines task_completion_status
milestone assignees participants subscribed labels discussion_locked time_estimate
total_time_spent reference author merged_at commit_count current_user_todos
- conflicts auto_merge_enabled approved_by
+ conflicts auto_merge_enabled approved_by source_branch_protected
+ default_merge_commit_message_with_description squash_on_merge available_auto_merge_strategies
+ has_ci mergeable commits_without_merge_commits security_auto_fix
]
if Gitlab.ee?
expected_fields << 'approved'
expected_fields << 'approvals_left'
expected_fields << 'approvals_required'
+ expected_fields << 'merge_trains_count'
end
expect(described_class).to have_graphql_fields(*expected_fields)
end
+ describe '#pipelines' do
+ subject { described_class.fields['pipelines'] }
+
+ it { is_expected.to have_attributes(max_page_size: 500) }
+ end
+
describe '#diff_stats_summary' do
subject { GitlabSchema.execute(query, context: { current_user: current_user }).as_json }
diff --git a/spec/graphql/types/permission_types/base_permission_type_spec.rb b/spec/graphql/types/permission_types/base_permission_type_spec.rb
index 2ce02f1520c..68632a509ee 100644
--- a/spec/graphql/types/permission_types/base_permission_type_spec.rb
+++ b/spec/graphql/types/permission_types/base_permission_type_spec.rb
@@ -11,9 +11,13 @@ RSpec.describe Types::PermissionTypes::BasePermissionType do
Class.new(described_class) do
graphql_name 'TestClass'
- permission_field :do_stuff, resolve: -> (_, _, _) { true }
+ permission_field :do_stuff
ability_field(:read_issue)
abilities :admin_issue
+
+ define_method :do_stuff do
+ true
+ end
end
end
diff --git a/spec/graphql/types/project_member_relation_enum_spec.rb b/spec/graphql/types/project_member_relation_enum_spec.rb
new file mode 100644
index 00000000000..3c947bf8406
--- /dev/null
+++ b/spec/graphql/types/project_member_relation_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::ProjectMemberRelationEnum do
+ specify { expect(described_class.graphql_name).to eq('ProjectMemberRelation') }
+
+ it 'exposes all the existing project member relation type values' do
+ expect(described_class.values.keys).to contain_exactly('DIRECT', 'INHERITED', 'DESCENDANTS', 'INVITED_GROUPS')
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index be579e92fb3..b3028e034cc 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['Project'] do
+ include GraphqlHelpers
+
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }
specify { expect(described_class.graphql_name).to eq('Project') }
@@ -28,7 +30,8 @@ RSpec.describe GitlabSchema.types['Project'] do
alert_management_alerts alert_management_alert alert_management_alert_status_counts
container_expiration_policy service_desk_enabled service_desk_address
issue_status_counts terraform_states alert_management_integrations
-
+ container_repositories container_repositories_count
+ pipeline_analytics total_pipeline_duration squash_read_only
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -76,6 +79,7 @@ RSpec.describe GitlabSchema.types['Project'] do
:merged_before,
:author_username,
:assignee_username,
+ :reviewer_username,
:milestone_title,
:sort
)
@@ -163,4 +167,32 @@ RSpec.describe GitlabSchema.types['Project'] do
end
it_behaves_like 'a GraphQL type with labels'
+
+ describe 'jira_imports' do
+ subject { resolve_field(:jira_imports, project) }
+
+ let_it_be(:project) { create(:project, :public) }
+
+ context 'when project has Jira imports' do
+ let_it_be(:jira_import1) { create(:jira_import_state, :finished, project: project, jira_project_key: 'AA', created_at: 2.days.ago) }
+ let_it_be(:jira_import2) { create(:jira_import_state, :finished, project: project, jira_project_key: 'BB', created_at: 5.days.ago) }
+
+ it 'retrieves the imports' do
+ expect(subject).to contain_exactly(jira_import1, jira_import2)
+ end
+ end
+
+ context 'when project does not have Jira imports' do
+ it 'returns an empty result' do
+ expect(subject).to be_empty
+ end
+ end
+ end
+
+ describe 'pipeline_analytics field' do
+ subject { described_class.fields['pipelineAnalytics'] }
+
+ it { is_expected.to have_graphql_type(Types::Ci::AnalyticsType) }
+ it { is_expected.to have_graphql_resolver(Resolvers::ProjectPipelineStatisticsResolver) }
+ end
end
diff --git a/spec/graphql/types/terraform/state_version_type_spec.rb b/spec/graphql/types/terraform/state_version_type_spec.rb
index 1c1e95039dc..18f869e4f1f 100644
--- a/spec/graphql/types/terraform/state_version_type_spec.rb
+++ b/spec/graphql/types/terraform/state_version_type_spec.rb
@@ -7,13 +7,15 @@ RSpec.describe GitlabSchema.types['TerraformStateVersion'] do
it { expect(described_class).to require_graphql_authorizations(:read_terraform_state) }
describe 'fields' do
- let(:fields) { %i[id created_by_user job created_at updated_at] }
+ let(:fields) { %i[id created_by_user job download_path serial created_at updated_at] }
it { expect(described_class).to have_graphql_fields(fields) }
it { expect(described_class.fields['id'].type).to be_non_null }
it { expect(described_class.fields['createdByUser'].type).not_to be_non_null }
it { expect(described_class.fields['job'].type).not_to be_non_null }
+ it { expect(described_class.fields['downloadPath'].type).not_to be_non_null }
+ it { expect(described_class.fields['serial'].type).not_to be_non_null }
it { expect(described_class.fields['createdAt'].type).to be_non_null }
it { expect(described_class.fields['updatedAt'].type).to be_non_null }
end
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index c8953d9ccb7..0eff33bb25b 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -15,14 +15,17 @@ RSpec.describe GitlabSchema.types['User'] do
name
username
email
+ publicEmail
avatarUrl
webUrl
webPath
todos
state
status
+ location
authoredMergeRequests
assignedMergeRequests
+ reviewRequestedMergeRequests
groupMemberships
groupCount
projectMemberships
diff --git a/spec/helpers/admin/user_actions_helper_spec.rb b/spec/helpers/admin/user_actions_helper_spec.rb
new file mode 100644
index 00000000000..7ccd9a4fe3e
--- /dev/null
+++ b/spec/helpers/admin/user_actions_helper_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Admin::UserActionsHelper do
+ describe '#admin_actions' do
+ let_it_be(:current_user) { build(:user) }
+
+ subject { helper.admin_actions(user) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(helper).to receive(:can?).with(current_user, :destroy_user, user).and_return(true)
+ end
+
+ context 'the user is a bot' do
+ let_it_be(:user) { build(:user, :bot) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'the current user and user are the same' do
+ let_it_be(:user) { build(:user) }
+ let_it_be(:current_user) { user }
+
+ it { is_expected.to contain_exactly("edit") }
+ end
+
+ context 'the user is a standard user' do
+ let_it_be(:user) { create(:user) }
+
+ it { is_expected.to contain_exactly("edit", "block", "deactivate", "delete", "delete_with_contributions") }
+ end
+
+ context 'the user is an admin user' do
+ let_it_be(:user) { create(:user, :admin) }
+
+ it { is_expected.to contain_exactly("edit", "block", "deactivate", "delete", "delete_with_contributions") }
+ end
+
+ context 'the user is blocked by LDAP' do
+ let_it_be(:user) { create(:omniauth_user, :ldap_blocked) }
+
+ it { is_expected.to contain_exactly("edit", "ldap", "delete", "delete_with_contributions") }
+ end
+
+ context 'the user is blocked pending approval' do
+ let_it_be(:user) { create(:user, :blocked_pending_approval) }
+
+ it { is_expected.to contain_exactly("edit", "approve", "reject") }
+ end
+
+ context 'the user is blocked' do
+ let_it_be(:user) { create(:user, :blocked) }
+
+ it { is_expected.to contain_exactly("edit", "unblock", "delete", "delete_with_contributions") }
+ end
+
+ context 'the user is deactivated' do
+ let_it_be(:user) { create(:user, :deactivated) }
+
+ it { is_expected.to contain_exactly("edit", "block", "activate", "delete", "delete_with_contributions") }
+ end
+
+ context 'the user is locked' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ user.lock_access!
+ end
+
+ it {
+ is_expected.to contain_exactly(
+ "edit",
+ "block",
+ "deactivate",
+ "unlock",
+ "delete",
+ "delete_with_contributions"
+ )
+ }
+ end
+
+ context 'the current_user does not have permission to delete the user' do
+ let_it_be(:user) { build(:user) }
+
+ before do
+ allow(helper).to receive(:can?).with(current_user, :destroy_user, user).and_return(false)
+ end
+
+ it { is_expected.to contain_exactly("edit", "block", "deactivate") }
+ end
+
+ context 'the user is a sole owner of a group' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it { is_expected.to contain_exactly("edit", "block", "deactivate") }
+ end
+ end
+end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index a557e9e04da..c7470f31ad8 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -116,9 +116,9 @@ RSpec.describe ApplicationHelper do
Time.use_zone('UTC') { example.run }
end
- def element(*arguments)
+ def element(**arguments)
@time = Time.zone.parse('2015-07-02 08:23')
- element = helper.time_ago_with_tooltip(@time, *arguments)
+ element = helper.time_ago_with_tooltip(@time, **arguments)
Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
end
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index b4cea7fb695..00c4a1880de 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -99,6 +99,22 @@ RSpec.describe AuthHelper do
end
end
+ describe 'experiment_enabled_button_based_providers' do
+ it 'returns the intersection set of github & google_oauth2 with enabled providers' do
+ allow(helper).to receive(:enabled_button_based_providers) { %w(twitter github google_oauth2) }
+
+ expect(helper.experiment_enabled_button_based_providers).to eq(%w(github google_oauth2))
+
+ allow(helper).to receive(:enabled_button_based_providers) { %w(google_oauth2 bitbucket) }
+
+ expect(helper.experiment_enabled_button_based_providers).to eq(%w(google_oauth2))
+
+ allow(helper).to receive(:enabled_button_based_providers) { %w(bitbucket) }
+
+ expect(helper.experiment_enabled_button_based_providers).to be_empty
+ end
+ end
+
describe 'button_based_providers_enabled?' do
before do
allow(helper).to receive(:auth_providers) { [:twitter, :github] }
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index cafe4c4275e..764c582e987 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -236,53 +236,41 @@ RSpec.describe BlobHelper do
let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
let(:blob) { fake_blob(path: Gitlab::FileDetector::PATTERNS[:gitlab_ci], data: data) }
- context 'feature enabled' do
- it 'is true' do
- expect(helper.show_suggest_pipeline_creation_celebration?).to be_truthy
- end
+ it 'is true' do
+ expect(helper.show_suggest_pipeline_creation_celebration?).to be_truthy
+ end
- context 'file is invalid format' do
- let(:data) { 'foo' }
+ context 'file is invalid format' do
+ let(:data) { 'foo' }
- it 'is false' do
- expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
- end
+ it 'is false' do
+ expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
+ end
- context 'does not use the default ci config' do
- before do
- project.ci_config_path = 'something_bad'
- end
-
- it 'is false' do
- expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
- end
+ context 'does not use the default ci config' do
+ before do
+ project.ci_config_path = 'something_bad'
end
- context 'does not have the needed cookie' do
- before do
- helper.request.cookies.delete "suggest_gitlab_ci_yml_commit_#{project.id}"
- end
-
- it 'is false' do
- expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
- end
+ it 'is false' do
+ expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
+ end
- context 'blob does not have auxiliary view' do
- before do
- allow(blob).to receive(:auxiliary_viewer).and_return(nil)
- end
+ context 'does not have the needed cookie' do
+ before do
+ helper.request.cookies.delete "suggest_gitlab_ci_yml_commit_#{project.id}"
+ end
- it 'is false' do
- expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
- end
+ it 'is false' do
+ expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
- context 'feature disabled' do
+ context 'blob does not have auxiliary view' do
before do
- stub_feature_flags(suggest_pipeline: false)
+ allow(blob).to receive(:auxiliary_viewer).and_return(nil)
end
it 'is false' do
@@ -294,10 +282,8 @@ RSpec.describe BlobHelper do
context 'when file is not a pipeline config file' do
let(:blob) { fake_blob(path: 'LICENSE') }
- context 'feature enabled' do
- it 'is false' do
- expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
- end
+ it 'is false' do
+ expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
end
diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb
index 6a5cb73281e..ecb9c98b1bf 100644
--- a/spec/helpers/button_helper_spec.rb
+++ b/spec/helpers/button_helper_spec.rb
@@ -89,7 +89,7 @@ RSpec.describe ButtonHelper do
it 'shows a warning on the dropdown description' do
description = element.search('.dropdown-menu-inner-content').first
- expect(description.inner_text).to eq "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+ expect(description.inner_text).to eq "You won't be able to pull or push repositories via SSH until you add an SSH key to your profile"
end
end
diff --git a/spec/helpers/calendar_helper_spec.rb b/spec/helpers/calendar_helper_spec.rb
index ceed4191ef4..08993dd1dd0 100644
--- a/spec/helpers/calendar_helper_spec.rb
+++ b/spec/helpers/calendar_helper_spec.rb
@@ -18,5 +18,14 @@ RSpec.describe CalendarHelper do
expect(helper.calendar_url_options[:feed_token]).to be_nil
end
end
+
+ context 'when feed token disabled' do
+ it "does not have a feed_token" do
+ current_user = create(:user)
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true)
+ expect(helper.calendar_url_options[:feed_token]).to be_nil
+ end
+ end
end
end
diff --git a/spec/helpers/ci/pipeline_schedules_helper_spec.rb b/spec/helpers/ci/pipeline_schedules_helper_spec.rb
deleted file mode 100644
index 2a81c2a44a0..00000000000
--- a/spec/helpers/ci/pipeline_schedules_helper_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Ci::PipelineSchedulesHelper, :aggregate_failures do
- describe '#timezone_data' do
- subject { helper.timezone_data }
-
- it 'matches schema' do
- expect(subject).not_to be_empty
- subject.each_with_index do |timzone_hash, i|
- expect(timzone_hash.keys).to contain_exactly(:name, :offset, :identifier), "Failed at index #{i}"
- end
- end
-
- it 'formats for display' do
- first_timezone = ActiveSupport::TimeZone.all[0]
-
- expect(subject[0][:name]).to eq(first_timezone.name)
- expect(subject[0][:offset]).to eq(first_timezone.now.utc_offset)
- expect(subject[0][:identifier]).to eq(first_timezone.tzinfo.identifier)
- end
- end
-end
diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb
index 38caae91ef2..6e41afac4ee 100644
--- a/spec/helpers/ci/runners_helper_spec.rb
+++ b/spec/helpers/ci/runners_helper_spec.rb
@@ -74,4 +74,57 @@ RSpec.describe Ci::RunnersHelper do
expect(data[:parent_shared_runners_availability]).to eq('enabled')
end
end
+
+ describe '#toggle_shared_runners_settings_data' do
+ let_it_be(:group) { create(:group) }
+ let(:project_with_runners) { create(:project, namespace: group, shared_runners_enabled: true) }
+ let(:project_without_runners) { create(:project, namespace: group, shared_runners_enabled: false) }
+
+ context 'when project has runners' do
+ it 'returns the correct value for is_enabled' do
+ data = toggle_shared_runners_settings_data(project_with_runners)
+ expect(data[:is_enabled]).to eq("true")
+ end
+ end
+
+ context 'when project does not have runners' do
+ it 'returns the correct value for is_enabled' do
+ data = toggle_shared_runners_settings_data(project_without_runners)
+ expect(data[:is_enabled]).to eq("false")
+ end
+ end
+
+ context 'for all projects' do
+ it 'returns the update path for toggling the shared runners setting' do
+ data = toggle_shared_runners_settings_data(project_with_runners)
+ expect(data[:update_path]).to eq(toggle_shared_runners_project_runners_path(project_with_runners))
+ end
+
+ it 'returns false for is_disabled_and_unoverridable when project has no group' do
+ project = create(:project)
+
+ data = toggle_shared_runners_settings_data(project)
+ expect(data[:is_disabled_and_unoverridable]).to eq("false")
+ end
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:shared_runners_setting, :is_disabled_and_unoverridable) do
+ 'enabled' | "false"
+ 'disabled_with_override' | "false"
+ 'disabled_and_unoverridable' | "true"
+ end
+
+ with_them do
+ it 'returns the override runner status for project with group' do
+ group = create(:group)
+ project = create(:project, group: group)
+ allow(group).to receive(:shared_runners_setting).and_return(shared_runners_setting)
+
+ data = toggle_shared_runners_settings_data(project)
+ expect(data[:is_disabled_and_unoverridable]).to eq(is_disabled_and_unoverridable)
+ end
+ end
+ end
+ end
end
diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb
index 6b08b6515cf..8c738141063 100644
--- a/spec/helpers/clusters_helper_spec.rb
+++ b/spec/helpers/clusters_helper_spec.rb
@@ -131,7 +131,7 @@ RSpec.describe ClustersHelper do
context 'other values' do
let(:cluster_type) { 'not_supported' }
- it 'Diplays generic cluster and reports error' do
+ it 'diplays generic cluster and reports error' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(
an_instance_of(ArgumentError),
cluster_error: { error: 'Cluster Type Missing', cluster_type: 'not_supported' }
diff --git a/spec/helpers/defer_script_tag_helper_spec.rb b/spec/helpers/defer_script_tag_helper_spec.rb
deleted file mode 100644
index 14317e353ab..00000000000
--- a/spec/helpers/defer_script_tag_helper_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe DeferScriptTagHelper do
- describe 'script tag' do
- script_url = 'test.js'
-
- it 'returns an script tag with defer=true' do
- expect(javascript_include_tag(script_url).to_s)
- .to eq "<script src=\"/javascripts/#{script_url}\" defer=\"defer\"></script>"
- end
- end
-end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index c085c3bdbd9..3580959fde0 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -358,30 +358,4 @@ RSpec.describe DiffHelper do
expect(diff_file_path_text(diff_file, max: 10)).to eq("...open.rb")
end
end
-
- describe 'unified_diff_lines_view_type' do
- before do
- controller.params[:view] = 'parallel'
- end
-
- describe 'unified diffs enabled' do
- before do
- stub_feature_flags(unified_diff_lines: true)
- end
-
- it 'returns inline view' do
- expect(helper.unified_diff_lines_view_type(project)).to eq 'inline'
- end
- end
-
- describe 'unified diffs disabled' do
- before do
- stub_feature_flags(unified_diff_lines: false)
- end
-
- it 'returns parallel view' do
- expect(helper.unified_diff_lines_view_type(project)).to eq :parallel
- end
- end
- end
end
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index ef8b342a3f6..58ed5901d45 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -296,7 +296,7 @@ RSpec.describe EmailsHelper do
end
with_them do
- it 'Produces the right List-Id' do
+ it 'produces the right List-Id' do
project = double("project")
allow(project).to receive(:full_path).and_return(full_path)
allow(project).to receive(:id).and_return(12345)
diff --git a/spec/helpers/gitlab_script_tag_helper_spec.rb b/spec/helpers/gitlab_script_tag_helper_spec.rb
new file mode 100644
index 00000000000..37413b9b1c2
--- /dev/null
+++ b/spec/helpers/gitlab_script_tag_helper_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabScriptTagHelper do
+ before do
+ allow(helper).to receive(:content_security_policy_nonce).and_return('noncevalue')
+ end
+
+ describe 'external script tag' do
+ let(:script_url) { 'test.js' }
+
+ it 'returns a script tag with defer=true and a nonce' do
+ expect(helper.javascript_include_tag(script_url).to_s)
+ .to eq "<script src=\"/javascripts/#{script_url}\" defer=\"defer\" nonce=\"noncevalue\"></script>"
+ end
+ end
+
+ describe 'inline script tag' do
+ let(:tag_with_nonce) {"<script nonce=\"noncevalue\">\n//<![CDATA[\nalert(1)\n//]]>\n</script>"}
+ let(:tag_with_nonce_and_type) {"<script type=\"application/javascript\" nonce=\"noncevalue\">\n//<![CDATA[\nalert(1)\n//]]>\n</script>"}
+
+ it 'returns a script tag with a nonce using block syntax' do
+ expect(helper.javascript_tag { 'alert(1)' }.to_s).to eq tag_with_nonce
+ end
+
+ it 'returns a script tag with a nonce using block syntax with options' do
+ expect(helper.javascript_tag(type: 'application/javascript') { 'alert(1)' }.to_s).to eq tag_with_nonce_and_type
+ end
+
+ it 'returns a script tag with a nonce using argument syntax' do
+ expect(helper.javascript_tag('alert(1)').to_s).to eq tag_with_nonce
+ end
+
+ it 'returns a script tag with a nonce using argument syntax with options' do
+ expect(helper.javascript_tag( 'alert(1)', type: 'application/javascript').to_s).to eq tag_with_nonce_and_type
+ end
+
+ # This scenario does not really make sense, but it's supported so we test it
+ it 'returns a script tag with a nonce using argument and block syntax with options' do
+ expect(helper.javascript_tag( '// ignored', type: 'application/javascript') { 'alert(1)' }.to_s).to eq tag_with_nonce_and_type
+ end
+ end
+end
diff --git a/spec/helpers/groups/group_members_helper_spec.rb b/spec/helpers/groups/group_members_helper_spec.rb
index bb92445cb19..222cca43860 100644
--- a/spec/helpers/groups/group_members_helper_spec.rb
+++ b/spec/helpers/groups/group_members_helper_spec.rb
@@ -74,13 +74,15 @@ RSpec.describe Groups::GroupMembersHelper do
before do
allow(helper).to receive(:group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id')
+ allow(helper).to receive(:can?).with(current_user, :admin_group_member, group).and_return(true)
end
it 'returns expected hash' do
expect(helper.group_members_list_data_attributes(group, present_members([group_member]))).to include({
members: helper.members_data_json(group, present_members([group_member])),
member_path: '/groups/foo-bar/-/group_members/:id',
- group_id: group.id
+ group_id: group.id,
+ can_manage_members: 'true'
})
end
end
diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb
index c05b2b206cc..4784d0aff26 100644
--- a/spec/helpers/icons_helper_spec.rb
+++ b/spec/helpers/icons_helper_spec.rb
@@ -5,21 +5,6 @@ require 'spec_helper'
RSpec.describe IconsHelper do
let(:icons_path) { ActionController::Base.helpers.image_path("icons.svg") }
- describe 'icon' do
- it 'returns aria-hidden by default' do
- star = icon('star')
-
- expect(star['aria-hidden']).to eq 'aria-hidden'
- end
-
- it 'does not return aria-hidden if aria-label is set' do
- up = icon('up', 'aria-label' => 'up')
-
- expect(up['aria-hidden']).to be_nil
- expect(up['aria-label']).to eq 'aria-label'
- end
- end
-
describe 'sprite_icon_path' do
it 'returns relative path' do
expect(sprite_icon_path).to eq(icons_path)
@@ -86,7 +71,7 @@ RSpec.describe IconsHelper do
it 'does not raise in production mode' do
stub_rails_env('production')
- expect(File).not_to receive(:read)
+ expect_file_not_to_read(Rails.root.join('node_modules/@gitlab/svgs/dist/icons.json'))
expect { sprite_icon(non_existing) }.not_to raise_error
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 0e3752f220e..57845904d32 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -44,6 +44,30 @@ RSpec.describe IssuablesHelper do
end
end
+ describe '#issuable_meta' do
+ let(:user) { create(:user) }
+
+ let_it_be(:project) { create(:project) }
+
+ describe 'author status' do
+ let(:issuable) { build(:merge_request, source_project: project, author: user, created_at: '2020-01-30') }
+
+ it 'displays an emoji if the user status is set' do
+ user.status = UserStatus.new(message: 'lol')
+ content = helper.issuable_meta(issuable, project)
+ expect(content).to match('<span class="user-status-emoji has-tooltip" title="lol" data-html="true" data-placement="top">')
+ expect(content).to match('<gl-emoji title="speech balloon" data-name="speech_balloon" data-unicode-version="6.0">')
+ end
+
+ it 'does not displays an emoji if the user status is not set' do
+ user.status = UserStatus.new
+ content = helper.issuable_meta(issuable, project)
+ expect(content).not_to match('class="user-status-emoji has-tooltip"')
+ expect(content).not_to match('gl-emoji')
+ end
+ end
+ end
+
describe '#issuables_state_counter_text' do
let(:user) { create(:user) }
@@ -194,7 +218,7 @@ RSpec.describe IssuablesHelper do
assign(:project, issue.project)
end
- it 'sets sentryIssueIdentifier to nil with no sentry issue ' do
+ it 'sets sentryIssueIdentifier to nil with no sentry issue' do
expect(helper.issuable_initial_data(issue)[:sentryIssueIdentifier])
.to be_nil
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 6c5855eeb91..45e8a2e7e1a 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -316,6 +316,7 @@ RSpec.describe MarkupHelper do
describe '#render_wiki_content' do
let(:wiki) { double('WikiPage', path: "file.#{extension}") }
let(:wiki_repository) { double('Repository') }
+ let(:content) { 'wiki content' }
let(:context) do
{
pipeline: :wiki, project: project, wiki: wiki,
@@ -325,9 +326,11 @@ RSpec.describe MarkupHelper do
end
before do
- expect(wiki).to receive(:content).and_return('wiki content')
+ expect(wiki).to receive(:content).and_return(content)
expect(wiki).to receive(:slug).and_return('nested/page')
expect(wiki).to receive(:repository).and_return(wiki_repository)
+ allow(wiki).to receive(:container).and_return(project)
+
helper.instance_variable_set(:@wiki, wiki)
end
@@ -339,6 +342,19 @@ RSpec.describe MarkupHelper do
helper.render_wiki_content(wiki)
end
+
+ context 'when context has labels' do
+ let_it_be(:label) { create(:label, title: 'Bug', project: project) }
+
+ let(:content) { '~Bug' }
+
+ it 'renders label' do
+ result = helper.render_wiki_content(wiki)
+ doc = Nokogiri::HTML.parse(result)
+
+ expect(doc.css('.gl-label-link')).not_to be_empty
+ end
+ end
end
context 'when file is Asciidoc' do
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 153dc19335b..377e2c43a72 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -6,26 +6,6 @@ RSpec.describe MergeRequestsHelper do
include ActionView::Helpers::UrlHelper
include ProjectForksHelper
- describe 'ci_build_details_path' do
- let(:project) { create(:project) }
- let(:merge_request) { MergeRequest.new }
- let(:ci_service) { CiService.new }
- let(:last_commit) { Ci::Pipeline.new({}) }
-
- before do
- allow(merge_request).to receive(:source_project).and_return(project)
- allow(merge_request).to receive(:last_commit).and_return(last_commit)
- allow(project).to receive(:ci_service).and_return(ci_service)
- allow(last_commit).to receive(:sha).and_return('12d65c')
- end
-
- it 'does not include api credentials in a link' do
- allow(ci_service)
- .to receive(:build_page).and_return("http://secretuser:secretpass@jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c")
- expect(helper.ci_build_details_path(merge_request)).not_to match("secret")
- end
- end
-
describe '#state_name_with_icon' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb
index b1c9485e5a1..c4795a814ba 100644
--- a/spec/helpers/nav_helper_spec.rb
+++ b/spec/helpers/nav_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe NavHelper, :do_not_mock_admin_mode do
+RSpec.describe NavHelper do
describe '#header_links' do
include_context 'custom session'
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index 5e37e56c612..555cffba614 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -20,10 +20,19 @@ RSpec.describe NotificationsHelper do
end
describe '#notification_event_name' do
- it { expect(notification_event_name(:success_pipeline)).to match('Successful pipeline') }
- it { expect(notification_event_name(:failed_pipeline)).to match('Failed pipeline') }
- it { expect(notification_event_name(:fixed_pipeline)).to match('Fixed pipeline') }
- it { expect(notification_event_name(:moved_project)).to match('Moved project') }
+ context 'for success_pipeline' do
+ it 'returns the custom name' do
+ expect(FastGettext).to receive(:cached_find).with('NotificationEvent|Successful pipeline')
+ expect(notification_event_name(:success_pipeline)).to eq('Successful pipeline')
+ end
+ end
+
+ context 'for everything else' do
+ it 'returns a humanized name' do
+ expect(FastGettext).to receive(:cached_find).with('NotificationEvent|Failed pipeline')
+ expect(notification_event_name(:failed_pipeline)).to eq('Failed pipeline')
+ end
+ end
end
describe '#notification_icon_level' do
diff --git a/spec/helpers/operations_helper_spec.rb b/spec/helpers/operations_helper_spec.rb
index 09f9bba8f9e..801d5de79b1 100644
--- a/spec/helpers/operations_helper_spec.rb
+++ b/spec/helpers/operations_helper_spec.rb
@@ -21,20 +21,15 @@ RSpec.describe OperationsHelper do
end
context 'initial service configuration' do
- let_it_be(:alerts_service) { AlertsService.new(project: project) }
let_it_be(:prometheus_service) { PrometheusService.new(project: project) }
before do
- allow(project).to receive(:find_or_initialize_service).with('alerts').and_return(alerts_service)
+ allow(project).to receive(:find_or_initialize_service).and_call_original
allow(project).to receive(:find_or_initialize_service).with('prometheus').and_return(prometheus_service)
end
it 'returns the correct values' do
expect(subject).to eq(
- 'activated' => 'false',
- 'url' => alerts_service.url,
- 'authorization_key' => nil,
- 'form_path' => project_service_path(project, alerts_service),
'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'),
'alerts_usage_url' => project_alert_management_index_path(project),
'prometheus_form_path' => project_service_path(project, prometheus_service),
@@ -104,33 +99,6 @@ RSpec.describe OperationsHelper do
end
end
end
-
- context 'with generic alerts service configured' do
- let_it_be(:alerts_service) { create(:alerts_service, project: project) }
-
- context 'with generic alerts enabled' do
- it 'returns the correct values' do
- expect(subject).to include(
- 'activated' => 'true',
- 'authorization_key' => alerts_service.token,
- 'url' => alerts_service.url
- )
- end
- end
-
- context 'with generic alerts disabled' do
- before do
- alerts_service.update!(active: false)
- end
-
- it 'returns the correct values' do
- expect(subject).to include(
- 'activated' => 'false',
- 'authorization_key' => alerts_service.token
- )
- end
- end
- end
end
describe '#operations_settings_data' do
diff --git a/spec/helpers/projects/alert_management_helper_spec.rb b/spec/helpers/projects/alert_management_helper_spec.rb
index f6d0c9ca49a..fd35c1ecab8 100644
--- a/spec/helpers/projects/alert_management_helper_spec.rb
+++ b/spec/helpers/projects/alert_management_helper_spec.rb
@@ -39,28 +39,6 @@ RSpec.describe Projects::AlertManagementHelper do
end
end
- context 'with alerts service' do
- let_it_be(:alerts_service) { create(:alerts_service, project: project) }
-
- context 'when alerts service is active' do
- it 'enables alert management' do
- expect(data).to include(
- 'alert-management-enabled' => 'true'
- )
- end
- end
-
- context 'when alerts service is inactive' do
- it 'disables alert management' do
- alerts_service.update!(active: false)
-
- expect(data).to include(
- 'alert-management-enabled' => 'false'
- )
- end
- end
- end
-
context 'with prometheus service' do
let_it_be(:prometheus_service) { create(:prometheus_service, project: project) }
@@ -105,6 +83,16 @@ RSpec.describe Projects::AlertManagementHelper do
end
end
+ context 'with an alert' do
+ let_it_be(:alert) { create(:alert_management_alert, project: project) }
+
+ it 'enables alert management' do
+ expect(data).to include(
+ 'alert-management-enabled' => 'true'
+ )
+ end
+ end
+
context 'when user does not have requisite enablement permissions' do
let(:user_can_enable_alert_management) { false }
diff --git a/spec/helpers/projects/terraform_helper_spec.rb b/spec/helpers/projects/terraform_helper_spec.rb
index de363c42d21..70b08f4139b 100644
--- a/spec/helpers/projects/terraform_helper_spec.rb
+++ b/spec/helpers/projects/terraform_helper_spec.rb
@@ -5,10 +5,11 @@ require 'spec_helper'
RSpec.describe Projects::TerraformHelper do
describe '#js_terraform_list_data' do
let_it_be(:project) { create(:project) }
+ let(:current_user) { project.creator }
- subject { helper.js_terraform_list_data(project) }
+ subject { helper.js_terraform_list_data(current_user, project) }
- it 'displays image path' do
+ it 'includes image path' do
image_path = ActionController::Base.helpers.image_path(
'illustrations/empty-state/empty-serverless-lg.svg'
)
@@ -16,8 +17,28 @@ RSpec.describe Projects::TerraformHelper do
expect(subject[:empty_state_image]).to eq(image_path)
end
- it 'displays project path' do
+ it 'includes project path' do
expect(subject[:project_path]).to eq(project.full_path)
end
+
+ it 'indicates the user is a terraform admin' do
+ expect(subject[:terraform_admin]).to eq(true)
+ end
+
+ context 'when current_user is not a terraform admin' do
+ let(:current_user) { create(:user) }
+
+ it 'indicates the user is not an admin' do
+ expect(subject[:terraform_admin]).to eq(false)
+ end
+ end
+
+ context 'when current_user is missing' do
+ let(:current_user) { nil }
+
+ it 'indicates the user is not an admin' do
+ expect(subject[:terraform_admin]).to be_nil
+ end
+ end
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 9635a6f9c82..d28d5ecda1b 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -416,6 +416,7 @@ RSpec.describe ProjectsHelper do
describe '#get_project_nav_tabs' do
before do
+ allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?) { true }
end
@@ -488,24 +489,37 @@ RSpec.describe ProjectsHelper do
describe '#can_view_operations_tab?' do
before do
allow(helper).to receive(:current_user).and_return(user)
+ allow(helper).to receive(:can?).and_return(false)
end
subject { helper.send(:can_view_operations_tab?, user, project) }
- [
- :metrics_dashboard,
- :read_alert_management_alert,
- :read_environment,
- :read_issue,
- :read_sentry_issue,
- :read_cluster
- ].each do |ability|
+ where(:ability) do
+ [
+ :metrics_dashboard,
+ :read_alert_management_alert,
+ :read_environment,
+ :read_issue,
+ :read_sentry_issue,
+ :read_cluster
+ ]
+ end
+
+ with_them do
it 'includes operations tab' do
- allow(helper).to receive(:can?).and_return(false)
allow(helper).to receive(:can?).with(user, ability, project).and_return(true)
is_expected.to be(true)
end
+
+ context 'when operations feature is disabled' do
+ it 'does not include operations tab' do
+ allow(helper).to receive(:can?).with(user, ability, project).and_return(true)
+ project.project_feature.update_attribute(:operations_access_level, ProjectFeature::DISABLED)
+
+ is_expected.to be(false)
+ end
+ end
end
end
@@ -1010,4 +1024,34 @@ RSpec.describe ProjectsHelper do
subject
end
end
+
+ describe '#project_permissions_settings' do
+ context 'with no project_setting associated' do
+ it 'includes a value for edit commit messages' do
+ settings = project_permissions_settings(project)
+
+ expect(settings[:allowEditingCommitMessages]).to be_falsy
+ end
+ end
+
+ context 'when commits are allowed to be edited' do
+ it 'includes the edit commit message value' do
+ project.create_project_setting(allow_editing_commit_messages: true)
+
+ settings = project_permissions_settings(project)
+
+ expect(settings[:allowEditingCommitMessages]).to be_truthy
+ end
+ end
+
+ context 'when commits are not allowed to be edited' do
+ it 'returns false to the edit commit message value' do
+ project.create_project_setting(allow_editing_commit_messages: false)
+
+ settings = project_permissions_settings(project)
+
+ expect(settings[:allowEditingCommitMessages]).to be_falsy
+ end
+ end
+ end
end
diff --git a/spec/helpers/rss_helper_spec.rb b/spec/helpers/rss_helper_spec.rb
index c7eb33dc6f7..05f6ebb6c1b 100644
--- a/spec/helpers/rss_helper_spec.rb
+++ b/spec/helpers/rss_helper_spec.rb
@@ -18,5 +18,14 @@ RSpec.describe RssHelper do
expect(helper.rss_url_options[:feed_token]).to be_nil
end
end
+
+ context 'when feed_token disabled' do
+ it "does not have a feed_token" do
+ current_user = create(:user)
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true)
+ expect(helper.rss_url_options[:feed_token]).to be_nil
+ end
+ end
end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 34af3ce7e5e..2cb9d66ac63 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -104,6 +104,37 @@ RSpec.describe SearchHelper do
})
end
+ it 'includes the users recently viewed issues with the exact same name', :aggregate_failures do
+ recent_issues = instance_double(::Gitlab::Search::RecentIssues)
+ expect(::Gitlab::Search::RecentIssues).to receive(:new).with(user: user).and_return(recent_issues)
+ project1 = create(:project, namespace: user.namespace)
+ project2 = create(:project, namespace: user.namespace)
+ issue1 = create(:issue, title: 'issue same_name', project: project1)
+ issue2 = create(:issue, title: 'issue same_name', project: project2)
+
+ expect(recent_issues).to receive(:search).with('the search term').and_return(Issue.id_in_ordered([issue1.id, issue2.id]))
+
+ results = search_autocomplete_opts("the search term")
+
+ expect(results.count).to eq(2)
+
+ expect(results[0]).to include({
+ category: 'Recent issues',
+ id: issue1.id,
+ label: 'issue same_name',
+ url: Gitlab::Routing.url_helpers.project_issue_path(issue1.project, issue1),
+ avatar_url: '' # This project didn't have an avatar so set this to ''
+ })
+
+ expect(results[1]).to include({
+ category: 'Recent issues',
+ id: issue2.id,
+ label: 'issue same_name',
+ url: Gitlab::Routing.url_helpers.project_issue_path(issue2.project, issue2),
+ avatar_url: '' # This project didn't have an avatar so set this to ''
+ })
+ end
+
it 'includes the users recently viewed merge requests', :aggregate_failures do
recent_merge_requests = instance_double(::Gitlab::Search::RecentMergeRequests)
expect(::Gitlab::Search::RecentMergeRequests).to receive(:new).with(user: user).and_return(recent_merge_requests)
@@ -502,11 +533,11 @@ RSpec.describe SearchHelper do
using RSpec::Parameterized::TableSyntax
where(:description, :expected) do
- 'test' | '<span class="gl-text-black-normal gl-font-weight-bold">test</span>'
- '<span style="color: blue;">this test should not be blue</span>' | '<span>this <span class="gl-text-black-normal gl-font-weight-bold">test</span> should not be blue</span>'
- '<a href="#" onclick="alert(\'XSS\')">Click Me test</a>' | '<a href="#">Click Me <span class="gl-text-black-normal gl-font-weight-bold">test</span></a>'
- '<script type="text/javascript">alert(\'Another XSS\');</script> test' | ' <span class="gl-text-black-normal gl-font-weight-bold">test</span>'
- 'Lorem test ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec.' | 'Lorem <span class="gl-text-black-normal gl-font-weight-bold">test</span> ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Don...'
+ 'test' | '<span class="gl-text-gray-900 gl-font-weight-bold">test</span>'
+ '<span style="color: blue;">this test should not be blue</span>' | '<span>this <span class="gl-text-gray-900 gl-font-weight-bold">test</span> should not be blue</span>'
+ '<a href="#" onclick="alert(\'XSS\')">Click Me test</a>' | '<a href="#">Click Me <span class="gl-text-gray-900 gl-font-weight-bold">test</span></a>'
+ '<script type="text/javascript">alert(\'Another XSS\');</script> test' | ' <span class="gl-text-gray-900 gl-font-weight-bold">test</span>'
+ 'Lorem test ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec.' | 'Lorem <span class="gl-text-gray-900 gl-font-weight-bold">test</span> ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Don...'
end
with_them do
diff --git a/spec/helpers/services_helper_spec.rb b/spec/helpers/services_helper_spec.rb
index d6b48b3d565..650642f8982 100644
--- a/spec/helpers/services_helper_spec.rb
+++ b/spec/helpers/services_helper_spec.rb
@@ -28,35 +28,72 @@ RSpec.describe ServicesHelper do
end
end
- describe '#group_level_integrations?' do
- subject { helper.group_level_integrations? }
+ describe '#scoped_reset_integration_path' do
+ let(:integration) { build_stubbed(:jira_service) }
+ let(:group) { nil }
+
+ subject { helper.scoped_reset_integration_path(integration, group: group) }
context 'when no group is present' do
- it { is_expected.to eq(false) }
+ it 'returns instance-level path' do
+ is_expected.to eq(reset_admin_application_settings_integration_path(integration))
+ end
end
context 'when group is present' do
let(:group) { build_stubbed(:group) }
- before do
- assign(:group, group)
+ it 'returns group-level path' do
+ is_expected.to eq(reset_group_settings_integration_path(group, integration))
end
+ end
+ end
+
+ describe '#reset_integration?' do
+ let(:group) { nil }
+
+ subject { helper.reset_integration?(integration, group: group) }
- context 'when `group_level_integrations` is not enabled' do
+ context 'when integration is existing record' do
+ let_it_be(:integration) { create(:jira_service) }
+
+ context 'when `reset_integrations` is not enabled' do
it 'returns false' do
- stub_feature_flags(group_level_integrations: false)
+ stub_feature_flags(reset_integrations: false)
is_expected.to eq(false)
end
end
- context 'when `group_level_integrations` is enabled for the group' do
+ context 'when `reset_integrations` is enabled' do
+ it 'returns true' do
+ stub_feature_flags(reset_integrations: true)
+
+ is_expected.to eq(true)
+ end
+ end
+
+ context 'when `reset_integrations` is enabled for a group' do
+ let(:group) { build_stubbed(:group) }
+
it 'returns true' do
- stub_feature_flags(group_level_integrations: group)
+ stub_feature_flags(reset_integrations: group)
is_expected.to eq(true)
end
end
end
+
+ context 'when integration is a new record' do
+ let_it_be(:integration) { build(:jira_service) }
+
+ context 'when `reset_integrations` is enabled' do
+ it 'returns false' do
+ stub_feature_flags(reset_integrations: true)
+
+ is_expected.to eq(false)
+ end
+ end
+ end
end
end
diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb
index 1c300aea2df..2d581dfba37 100644
--- a/spec/helpers/sorting_helper_spec.rb
+++ b/spec/helpers/sorting_helper_spec.rb
@@ -77,6 +77,7 @@ RSpec.describe SortingHelper do
sort_value_latest_activity => sort_title_latest_activity,
sort_value_recently_created => sort_title_created_date,
sort_value_name => sort_title_name,
+ sort_value_name_desc => sort_title_name_desc,
sort_value_stars_desc => sort_title_stars
}
end
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index 255ec2a41b4..2cec7203fe1 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -32,10 +32,12 @@ RSpec.describe StorageHelper do
wiki_size: 10.bytes,
lfs_objects_size: 20.gigabytes,
build_artifacts_size: 30.megabytes,
- snippets_size: 40.megabytes))
+ snippets_size: 40.megabytes,
+ packages_size: 12.megabytes,
+ uploads_size: 15.megabytes))
end
- let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB / Snippets: 40 MB' }
+ let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB / Snippets: 40 MB / Packages: 12 MB / Uploads: 15 MB' }
it 'works on ProjectStatistics' do
expect(helper.storage_counters_details(project.statistics)).to eq(message)
diff --git a/spec/helpers/time_zone_helper_spec.rb b/spec/helpers/time_zone_helper_spec.rb
new file mode 100644
index 00000000000..391e9bd38ed
--- /dev/null
+++ b/spec/helpers/time_zone_helper_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe TimeZoneHelper, :aggregate_failures do
+ describe '#timezone_data' do
+ context 'with short format' do
+ subject(:timezone_data) { helper.timezone_data }
+
+ it 'matches schema' do
+ expect(timezone_data).not_to be_empty
+
+ timezone_data.each_with_index do |timezone_hash, i|
+ expect(timezone_hash.keys).to contain_exactly(
+ :identifier,
+ :name,
+ :offset
+ ), "Failed at index #{i}"
+ end
+ end
+
+ it 'formats for display' do
+ tz = ActiveSupport::TimeZone.all[0]
+
+ expect(timezone_data[0]).to eq(
+ identifier: tz.tzinfo.identifier,
+ name: tz.name,
+ offset: tz.now.utc_offset
+ )
+ end
+ end
+
+ context 'with full format' do
+ subject(:timezone_data) { helper.timezone_data(format: :full) }
+
+ it 'matches schema' do
+ expect(timezone_data).not_to be_empty
+
+ timezone_data.each_with_index do |timezone_hash, i|
+ expect(timezone_hash.keys).to contain_exactly(
+ :identifier,
+ :name,
+ :abbr,
+ :offset,
+ :formatted_offset
+ ), "Failed at index #{i}"
+ end
+ end
+
+ it 'formats for display' do
+ tz = ActiveSupport::TimeZone.all[0]
+
+ expect(timezone_data[0]).to eq(
+ identifier: tz.tzinfo.identifier,
+ name: tz.name,
+ abbr: tz.tzinfo.strftime('%Z'),
+ offset: tz.now.utc_offset,
+ formatted_offset: tz.now.formatted_offset
+ )
+ end
+ end
+
+ context 'with unknown format' do
+ subject(:timezone_data) { helper.timezone_data(format: :unknown) }
+
+ it 'raises an exception' do
+ expect { timezone_data }.to raise_error ArgumentError, 'Invalid format :unknown. Valid formats are :short, :full.'
+ end
+ end
+ end
+end
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
index 620bf248d7b..136ec07e73d 100644
--- a/spec/helpers/tree_helper_spec.rb
+++ b/spec/helpers/tree_helper_spec.rb
@@ -216,6 +216,24 @@ RSpec.describe TreeHelper do
web_ide_url: "/-/ide/project/#{project.full_path}/edit/#{sha}/-/#{@path}"
)
end
+
+ it 'does not load blob from repository again' do
+ blob
+
+ expect(repository).not_to receive(:blob_at)
+
+ subject
+ end
+ end
+
+ context 'nil blob is passed' do
+ let(:blob) { nil }
+
+ it 'does not load blob from repository' do
+ expect(repository).not_to receive(:blob_at)
+
+ subject
+ end
end
context 'user does not have write access but a personal fork exists' do
diff --git a/spec/helpers/user_callouts_helper_spec.rb b/spec/helpers/user_callouts_helper_spec.rb
index 4ab3be877b4..250aedda906 100644
--- a/spec/helpers/user_callouts_helper_spec.rb
+++ b/spec/helpers/user_callouts_helper_spec.rb
@@ -167,8 +167,20 @@ RSpec.describe UserCalloutsHelper do
subject { helper.show_registration_enabled_user_callout? }
+ context 'when on gitlab.com' do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(true)
+ allow(helper).to receive(:current_user).and_return(admin)
+ stub_application_setting(signup_enabled: true)
+ allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { false }
+ end
+
+ it { is_expected.to be false }
+ end
+
context 'when `current_user` is not an admin' do
before do
+ allow(::Gitlab).to receive(:com?).and_return(false)
allow(helper).to receive(:current_user).and_return(user)
stub_application_setting(signup_enabled: true)
allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { false }
@@ -179,6 +191,7 @@ RSpec.describe UserCalloutsHelper do
context 'when signup is disabled' do
before do
+ allow(::Gitlab).to receive(:com?).and_return(false)
allow(helper).to receive(:current_user).and_return(admin)
stub_application_setting(signup_enabled: false)
allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { false }
@@ -189,6 +202,7 @@ RSpec.describe UserCalloutsHelper do
context 'when user has dismissed callout' do
before do
+ allow(::Gitlab).to receive(:com?).and_return(false)
allow(helper).to receive(:current_user).and_return(admin)
stub_application_setting(signup_enabled: true)
allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { true }
@@ -197,8 +211,9 @@ RSpec.describe UserCalloutsHelper do
it { is_expected.to be false }
end
- context 'when `current_user` is an admin, signup is enabled, and user has not dismissed callout' do
+ context 'when not gitlab.com, `current_user` is an admin, signup is enabled, and user has not dismissed callout' do
before do
+ allow(::Gitlab).to receive(:com?).and_return(false)
allow(helper).to receive(:current_user).and_return(admin)
stub_application_setting(signup_enabled: true)
allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { false }
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index 9ebbf975903..c92c6e6e78e 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -272,4 +272,82 @@ RSpec.describe UsersHelper do
end
end
end
+
+ describe '#user_display_name' do
+ subject { helper.user_display_name(user) }
+
+ before do
+ stub_current_user(nil)
+ end
+
+ context 'for a confirmed user' do
+ let(:user) { create(:user) }
+
+ before do
+ stub_profile_permission_allowed(true)
+ end
+
+ it { is_expected.to eq(user.name) }
+ end
+
+ context 'for an unconfirmed user' do
+ let(:user) { create(:user, :unconfirmed) }
+
+ before do
+ stub_profile_permission_allowed(false)
+ end
+
+ it { is_expected.to eq('Unconfirmed user') }
+
+ context 'when current user is an admin' do
+ before do
+ admin_user = create(:admin)
+ stub_current_user(admin_user)
+ stub_profile_permission_allowed(true, admin_user)
+ end
+
+ it { is_expected.to eq(user.name) }
+ end
+
+ context 'when the current user is self' do
+ before do
+ stub_current_user(user)
+ stub_profile_permission_allowed(true, user)
+ end
+
+ it { is_expected.to eq(user.name) }
+ end
+ end
+
+ context 'for a blocked user' do
+ let(:user) { create(:user, :blocked) }
+
+ it { is_expected.to eq('Blocked user') }
+ end
+
+ def stub_current_user(user)
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ def stub_profile_permission_allowed(allowed, current_user = nil)
+ allow(helper).to receive(:can?).with(current_user, :read_user_profile, user).and_return(allowed)
+ end
+ end
+
+ describe '#admin_users_data_attributes' do
+ subject(:data) { helper.admin_users_data_attributes([user]) }
+
+ it 'users matches the serialized json' do
+ entity = double
+ expect_next_instance_of(Admin::UserSerializer) do |instance|
+ expect(instance).to receive(:represent).with([user]).and_return(entity)
+ end
+ expect(entity).to receive(:to_json).and_return("{\"username\":\"admin\"}")
+ expect(data[:users]).to eq "{\"username\":\"admin\"}"
+ end
+
+ it 'paths matches the schema' do
+ expect(data[:paths]).to match_schema('entities/admin_users_data_attributes_paths')
+ end
+ end
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index cd1fc70bbc1..86b0693af92 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -284,4 +284,34 @@ RSpec.describe VisibilityLevelHelper do
it { is_expected.to eq(expected) }
end
end
+
+ describe '#visibility_level_options' do
+ let(:user) { build(:user) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ it 'returns the desired mapping' do
+ expected_options = [
+ {
+ level: 0,
+ label: 'Private',
+ description: 'The group and its projects can only be viewed by members.'
+ },
+ {
+ level: 10,
+ label: 'Internal',
+ description: 'The group and any internal projects can be viewed by any logged in user except external users.'
+ },
+ {
+ level: 20,
+ label: 'Public',
+ description: 'The group and any public projects can be viewed without any authentication.'
+ }
+ ]
+
+ expect(helper.visibility_level_options(group)).to eq expected_options
+ end
+ end
end
diff --git a/spec/helpers/whats_new_helper_spec.rb b/spec/helpers/whats_new_helper_spec.rb
index 1c8684de75c..017826921ff 100644
--- a/spec/helpers/whats_new_helper_spec.rb
+++ b/spec/helpers/whats_new_helper_spec.rb
@@ -3,22 +3,22 @@
require 'spec_helper'
RSpec.describe WhatsNewHelper do
- let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
-
describe '#whats_new_storage_key' do
subject { helper.whats_new_storage_key }
context 'when version exist' do
+ let(:release_item) { double(:item) }
+
before do
- allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
+ allow(ReleaseHighlight).to receive(:versions).and_return([84.0])
end
- it { is_expected.to eq('display-whats-new-notification-01.05') }
+ it { is_expected.to eq('display-whats-new-notification-84.0') }
end
- context 'when recent release items do NOT exist' do
+ context 'when most recent release highlights do NOT exist' do
before do
- allow(helper).to receive(:whats_new_release_items).and_return(nil)
+ allow(ReleaseHighlight).to receive(:versions).and_return(nil)
end
it { is_expected.to be_nil }
@@ -30,31 +30,28 @@ RSpec.describe WhatsNewHelper do
context 'when recent release items exist' do
it 'returns the count from the most recent file' do
- expect(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
+ allow(ReleaseHighlight).to receive(:most_recent_item_count).and_return(1)
expect(subject).to eq(1)
end
end
context 'when recent release items do NOT exist' do
- before do
- allow(YAML).to receive(:safe_load).and_raise
+ it 'returns nil' do
+ allow(ReleaseHighlight).to receive(:most_recent_item_count).and_return(nil)
- expect(Gitlab::ErrorTracking).to receive(:track_exception)
- end
-
- it 'fails gracefully and logs an error' do
expect(subject).to be_nil
end
end
end
- # Testing this important private method here because the request spec required multiple confusing mocks and felt wrong and overcomplicated
- describe '#whats_new_items_cache_key' do
- it 'returns a key containing the most recent file name and page parameter' do
- allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
+ describe '#whats_new_versions' do
+ let(:versions) { [84.0] }
+
+ it 'returns ReleaseHighlight.versions' do
+ expect(ReleaseHighlight).to receive(:versions).and_return(versions)
- expect(helper.send(:whats_new_items_cache_key, 2)).to eq('whats_new:release_items:file-20201225_01_05:page-2')
+ expect(helper.whats_new_versions).to eq(versions)
end
end
end
diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb
index de722764bf4..d5f9ef569c7 100644
--- a/spec/initializers/lograge_spec.rb
+++ b/spec/initializers/lograge_spec.rb
@@ -153,32 +153,22 @@ RSpec.describe 'lograge', type: :request do
end
end
- context 'with transaction' do
- let(:transaction) { Gitlab::Metrics::WebTransaction.new({}) }
-
- before do
- allow(Gitlab::Metrics::Transaction).to receive(:current).and_return(transaction)
- end
-
+ context 'with db payload' do
context 'when RequestStore is enabled', :request_store do
- context 'with db payload' do
- it 'includes db counters', :request_store do
- ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
- subscriber.process_action(event)
+ it 'includes db counters' do
+ ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
+ subscriber.process_action(event)
- expect(log_data).to include("db_count" => 1, "db_write_count" => 0, "db_cached_count" => 0)
- end
+ expect(log_data).to include("db_count" => a_value >= 1, "db_write_count" => 0, "db_cached_count" => 0)
end
end
context 'when RequestStore is disabled' do
- context 'with db payload' do
- it 'does not include db counters' do
- ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
- subscriber.process_action(event)
+ it 'does not include db counters' do
+ ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
+ subscriber.process_action(event)
- expect(log_data).not_to include("db_count" => 1, "db_write_count" => 0, "db_cached_count" => 0)
- end
+ expect(log_data).not_to include("db_count", "db_write_count", "db_cached_count")
end
end
end
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index 362371e0962..ab16dbad3fc 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -11,17 +11,20 @@ RSpec.describe 'create_tokens' do
let(:rsa_key) { /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m.freeze }
before do
- allow(File).to receive(:write)
- allow(File).to receive(:delete)
allow(Rails).to receive_message_chain(:application, :secrets).and_return(secrets)
allow(Rails).to receive_message_chain(:root, :join) { |string| string }
+
+ allow(File).to receive(:write).and_call_original
+ allow(File).to receive(:write).with(Rails.root.join('config/secrets.yml'))
+ allow(File).to receive(:delete).and_call_original
+ allow(File).to receive(:delete).with(Rails.root.join('.secret'))
allow(self).to receive(:warn)
allow(self).to receive(:exit)
end
describe 'ensure acknowledged secrets in any installations' do
let(:acknowledged_secrets) do
- %w[secret_key_base otp_key_base db_key_base openid_connect_signing_key]
+ %w[secret_key_base otp_key_base db_key_base openid_connect_signing_key encrypted_settings_key_base rotated_encrypted_settings_key_base]
end
it 'does not allow to add a new secret without a proper handling' do
@@ -87,6 +90,7 @@ RSpec.describe 'create_tokens' do
expect(new_secrets['otp_key_base']).to eq(secrets.otp_key_base)
expect(new_secrets['db_key_base']).to eq(secrets.db_key_base)
expect(new_secrets['openid_connect_signing_key']).to eq(secrets.openid_connect_signing_key)
+ expect(new_secrets['encrypted_settings_key_base']).to eq(secrets.encrypted_settings_key_base)
end
create_tokens
@@ -103,9 +107,10 @@ RSpec.describe 'create_tokens' do
before do
secrets.db_key_base = 'db_key_base'
secrets.openid_connect_signing_key = 'openid_connect_signing_key'
+ secrets.encrypted_settings_key_base = 'encrypted_settings_key_base'
allow(File).to receive(:exist?).with('.secret').and_return(true)
- allow(File).to receive(:read).with('.secret').and_return('file_key')
+ stub_file_read('.secret', content: 'file_key')
end
context 'when secret_key_base exists in the environment and secrets.yml' do
@@ -155,6 +160,7 @@ RSpec.describe 'create_tokens' do
expect(secrets.otp_key_base).to eq('otp_key_base')
expect(secrets.db_key_base).to eq('db_key_base')
expect(secrets.openid_connect_signing_key).to eq('openid_connect_signing_key')
+ expect(secrets.encrypted_settings_key_base).to eq('encrypted_settings_key_base')
end
it 'deletes the .secret file' do
@@ -205,12 +211,34 @@ RSpec.describe 'create_tokens' do
create_tokens
end
end
+
+ context 'when rotated_encrypted_settings_key_base does not exist' do
+ before do
+ secrets.secret_key_base = 'secret_key_base'
+ secrets.otp_key_base = 'otp_key_base'
+ secrets.openid_connect_signing_key = 'openid_connect_signing_key'
+ secrets.encrypted_settings_key_base = 'encrypted_settings_key_base'
+ end
+
+ it 'does not warn about the missing secrets' do
+ expect(self).not_to receive(:warn_missing_secret).with('rotated_encrypted_settings_key_base')
+
+ create_tokens
+ end
+
+ it 'does not update secrets.yml' do
+ expect(File).not_to receive(:write)
+
+ create_tokens
+ end
+ end
end
context 'when db_key_base is blank but exists in secrets.yml' do
before do
secrets.otp_key_base = 'otp_key_base'
secrets.secret_key_base = 'secret_key_base'
+ secrets.encrypted_settings_key_base = 'encrypted_settings_key_base'
yaml_secrets = secrets.to_h.stringify_keys.merge('db_key_base' => '<%= an_erb_expression %>')
allow(File).to receive(:exist?).with('.secret').and_return(false)
diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_browser_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_browser_spec.js
deleted file mode 100644
index 4e06e5c12fc..00000000000
--- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_browser_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-// this file can't be migrated to jest because it relies on the browser to perform integration tests:
-// see: https://gitlab.com/gitlab-org/gitlab/-/issues/194207#note_301878738
-import { FIXTURES_PATH } from 'spec/test_constants';
-import BalsamiqViewer from '~/blob/balsamiq/balsamiq_viewer';
-
-const bmprPath = `${FIXTURES_PATH}/blob/balsamiq/test.bmpr`;
-
-describe('Balsamiq integration spec', () => {
- let container;
- let endpoint;
- let balsamiqViewer;
-
- preloadFixtures('static/balsamiq_viewer.html');
-
- beforeEach(() => {
- loadFixtures('static/balsamiq_viewer.html');
-
- container = document.getElementById('js-balsamiq-viewer');
- balsamiqViewer = new BalsamiqViewer(container);
- });
-
- describe('successful response', () => {
- beforeEach(done => {
- endpoint = bmprPath;
-
- balsamiqViewer
- .loadFile(endpoint)
- .then(done)
- .catch(done.fail);
- });
-
- it('does not show loading icon', () => {
- expect(document.querySelector('.loading')).toBeNull();
- });
-
- it('renders the balsamiq previews', () => {
- expect(document.querySelectorAll('.previews .preview').length).not.toEqual(0);
- });
- });
-
- describe('error getting file', () => {
- beforeEach(done => {
- endpoint = 'invalid/path/to/file.bmpr';
-
- balsamiqViewer
- .loadFile(endpoint)
- .then(done.fail, null)
- .catch(done);
- });
-
- it('does not show loading icon', () => {
- expect(document.querySelector('.loading')).toBeNull();
- });
-
- it('does not render the balsamiq previews', () => {
- expect(document.querySelectorAll('.previews .preview').length).toEqual(0);
- });
- });
-});
diff --git a/spec/lib/api/entities/merge_request_basic_spec.rb b/spec/lib/api/entities/merge_request_basic_spec.rb
index 715fcf4bcdb..fe4c27b70ae 100644
--- a/spec/lib/api/entities/merge_request_basic_spec.rb
+++ b/spec/lib/api/entities/merge_request_basic_spec.rb
@@ -40,4 +40,31 @@ RSpec.describe ::API::Entities::MergeRequestBasic do
expect(batch.count).to be_within(3 * query.count).of(control.count)
end
end
+
+ context 'reviewers' do
+ context "when merge_request_reviewers FF is enabled" do
+ before do
+ stub_feature_flags(merge_request_reviewers: true)
+ merge_request.reviewers = [user]
+ end
+
+ it 'includes assigned reviewers' do
+ result = Gitlab::Json.parse(present(merge_request).to_json)
+
+ expect(result['reviewers'][0]['username']).to eq user.username
+ end
+ end
+
+ context "when merge_request_reviewers FF is disabled" do
+ before do
+ stub_feature_flags(merge_request_reviewers: false)
+ end
+
+ it 'does not include reviewers' do
+ result = Gitlab::Json.parse(present(merge_request).to_json)
+
+ expect(result.keys).not_to include('reviewers')
+ end
+ end
+ end
end
diff --git a/spec/lib/api/helpers/sse_helpers_spec.rb b/spec/lib/api/helpers/sse_helpers_spec.rb
new file mode 100644
index 00000000000..397051d9142
--- /dev/null
+++ b/spec/lib/api/helpers/sse_helpers_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::SSEHelpers do
+ include Gitlab::Routing
+
+ let_it_be(:project) { create(:project) }
+
+ subject { Class.new.include(described_class).new }
+
+ describe '#request_from_sse?' do
+ before do
+ allow(subject).to receive(:request).and_return(request)
+ end
+
+ context 'when referer is nil' do
+ let(:request) { double(referer: nil)}
+
+ it 'returns false' do
+ expect(URI).not_to receive(:parse)
+ expect(subject.request_from_sse?(project)).to eq false
+ end
+ end
+
+ context 'when referer is not from SSE' do
+ let(:request) { double(referer: 'https://gitlab.com')}
+
+ it 'returns false' do
+ expect(URI).to receive(:parse).and_call_original
+ expect(subject.request_from_sse?(project)).to eq false
+ end
+ end
+
+ context 'when referer is from SSE' do
+ let(:request) { double(referer: project_show_sse_path(project, 'master/README.md'))}
+
+ it 'returns true' do
+ expect(URI).to receive(:parse).and_call_original
+ expect(subject.request_from_sse?(project)).to eq true
+ end
+ end
+ end
+end
diff --git a/spec/lib/api/validations/validators/integer_or_custom_value_spec.rb b/spec/lib/api/validations/validators/integer_or_custom_value_spec.rb
new file mode 100644
index 00000000000..a04917736db
--- /dev/null
+++ b/spec/lib/api/validations/validators/integer_or_custom_value_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Validations::Validators::IntegerOrCustomValue do
+ include ApiValidatorsHelpers
+
+ let(:custom_values) { %w[None Any Started Current] }
+
+ subject { described_class.new(['test'], { values: custom_values }, false, scope.new) }
+
+ context 'valid parameters' do
+ it 'does not raise a validation error' do
+ expect_no_validation_error('test' => 2)
+ expect_no_validation_error('test' => 100)
+ expect_no_validation_error('test' => 'None')
+ expect_no_validation_error('test' => 'Any')
+ expect_no_validation_error('test' => 'none')
+ expect_no_validation_error('test' => 'any')
+ expect_no_validation_error('test' => 'started')
+ expect_no_validation_error('test' => 'CURRENT')
+ end
+
+ context 'when custom values is empty and value is an integer' do
+ let(:custom_values) { [] }
+
+ it 'does not raise a validation error' do
+ expect_no_validation_error({ 'test' => 5 })
+ end
+ end
+ end
+
+ context 'invalid parameters' do
+ it 'raises a validation error' do
+ expect_validation_error({ 'test' => 'Upcomming' })
+ end
+
+ context 'when custom values is empty and value is not an integer' do
+ let(:custom_values) { [] }
+
+ it 'raises a validation error' do
+ expect_validation_error({ 'test' => '5' })
+ end
+ end
+ end
+end
diff --git a/spec/lib/atlassian/jira_connect/client_spec.rb b/spec/lib/atlassian/jira_connect/client_spec.rb
index cefd1fa3274..6a161854dfb 100644
--- a/spec/lib/atlassian/jira_connect/client_spec.rb
+++ b/spec/lib/atlassian/jira_connect/client_spec.rb
@@ -7,8 +7,10 @@ RSpec.describe Atlassian::JiraConnect::Client do
subject { described_class.new('https://gitlab-test.atlassian.net', 'sample_secret') }
+ let_it_be(:project) { create_default(:project, :repository) }
+
around do |example|
- Timecop.freeze { example.run }
+ freeze_time { example.run }
end
describe '.generate_update_sequence_id' do
@@ -19,41 +21,158 @@ RSpec.describe Atlassian::JiraConnect::Client do
end
end
- describe '#store_dev_info' do
- let_it_be(:project) { create_default(:project, :repository) }
- let_it_be(:merge_requests) { create_list(:merge_request, 2, :unique_branches) }
+ describe '#send_info' do
+ it 'calls store_build_info and store_dev_info as appropriate' do
+ expect(subject).to receive(:store_build_info).with(
+ project: project,
+ update_sequence_id: :x,
+ pipelines: :y
+ ).and_return(:build_stored)
+
+ expect(subject).to receive(:store_dev_info).with(
+ project: project,
+ update_sequence_id: :x,
+ commits: :a,
+ branches: :b,
+ merge_requests: :c
+ ).and_return(:dev_stored)
+
+ args = {
+ project: project,
+ update_sequence_id: :x,
+ commits: :a,
+ branches: :b,
+ merge_requests: :c,
+ pipelines: :y
+ }
+
+ expect(subject.send_info(**args)).to contain_exactly(:dev_stored, :build_stored)
+ end
- let(:expected_jwt) do
- Atlassian::Jwt.encode(
- Atlassian::Jwt.build_claims(
- Atlassian::JiraConnect.app_key,
- '/rest/devinfo/0.10/bulk',
- 'POST'
- ),
- 'sample_secret'
- )
+ it 'only calls methods that we need to call' do
+ expect(subject).to receive(:store_dev_info).with(
+ project: project,
+ update_sequence_id: :x,
+ commits: :a
+ ).and_return(:dev_stored)
+
+ args = {
+ project: project,
+ update_sequence_id: :x,
+ commits: :a
+ }
+
+ expect(subject.send_info(**args)).to contain_exactly(:dev_stored)
+ end
+
+ it 'raises an argument error if there is nothing to send (probably a typo?)' do
+ expect { subject.send_info(project: project, builds: :x) }
+ .to raise_error(ArgumentError)
+ end
+ end
+
+ def expected_headers(path)
+ expected_jwt = Atlassian::Jwt.encode(
+ Atlassian::Jwt.build_claims(Atlassian::JiraConnect.app_key, path, 'POST'),
+ 'sample_secret'
+ )
+
+ {
+ 'Authorization' => "JWT #{expected_jwt}",
+ 'Content-Type' => 'application/json'
+ }
+ end
+
+ describe '#store_build_info' do
+ let_it_be(:mrs_by_title) { create_list(:merge_request, 4, :unique_branches, :jira_title) }
+ let_it_be(:mrs_by_branch) { create_list(:merge_request, 2, :jira_branch) }
+ let_it_be(:red_herrings) { create_list(:merge_request, 1, :unique_branches) }
+
+ let_it_be(:pipelines) do
+ (red_herrings + mrs_by_branch + mrs_by_title).map do |mr|
+ create(:ci_pipeline, merge_request: mr)
+ end
+ end
+
+ let(:build_info_payload_schema) do
+ Atlassian::Schemata.build_info_payload
+ end
+
+ let(:body) do
+ matcher = be_valid_json.according_to_schema(build_info_payload_schema)
+
+ ->(text) { matcher.matches?(text) }
end
before do
- stub_full_request('https://gitlab-test.atlassian.net/rest/devinfo/0.10/bulk', method: :post)
- .with(
- headers: {
- 'Authorization' => "JWT #{expected_jwt}",
- 'Content-Type' => 'application/json'
- }
- )
+ path = '/rest/builds/0.1/bulk'
+ stub_full_request('https://gitlab-test.atlassian.net' + path, method: :post)
+ .with(body: body, headers: expected_headers(path))
+ end
+
+ it "calls the API with auth headers" do
+ subject.send(:store_build_info, project: project, pipelines: pipelines)
+ end
+
+ it 'only sends information about relevant MRs' do
+ expect(subject).to receive(:post).with('/rest/builds/0.1/bulk', { builds: have_attributes(size: 6) })
+
+ subject.send(:store_build_info, project: project, pipelines: pipelines)
+ end
+
+ it 'does not call the API if there is nothing to report' do
+ expect(subject).not_to receive(:post)
+
+ subject.send(:store_build_info, project: project, pipelines: pipelines.take(1))
+ end
+
+ it 'does not call the API if the feature flag is not enabled' do
+ stub_feature_flags(jira_sync_builds: false)
+
+ expect(subject).not_to receive(:post)
+
+ subject.send(:store_build_info, project: project, pipelines: pipelines)
+ end
+
+ it 'does call the API if the feature flag enabled for the project' do
+ stub_feature_flags(jira_sync_builds: project)
+
+ expect(subject).to receive(:post).with('/rest/builds/0.1/bulk', { builds: Array })
+
+ subject.send(:store_build_info, project: project, pipelines: pipelines)
+ end
+
+ it 'avoids N+1 database queries' do
+ baseline = ActiveRecord::QueryRecorder.new do
+ subject.send(:store_build_info, project: project, pipelines: pipelines)
+ end
+
+ pipelines << create(:ci_pipeline, head_pipeline_of: create(:merge_request, :jira_branch))
+
+ expect { subject.send(:store_build_info, project: project, pipelines: pipelines) }.not_to exceed_query_limit(baseline)
+ end
+ end
+
+ describe '#store_dev_info' do
+ let_it_be(:merge_requests) { create_list(:merge_request, 2, :unique_branches) }
+
+ before do
+ path = '/rest/devinfo/0.10/bulk'
+
+ stub_full_request('https://gitlab-test.atlassian.net' + path, method: :post)
+ .with(headers: expected_headers(path))
end
it "calls the API with auth headers" do
- subject.store_dev_info(project: project)
+ subject.send(:store_dev_info, project: project)
end
it 'avoids N+1 database queries' do
- control_count = ActiveRecord::QueryRecorder.new { subject.store_dev_info(project: project, merge_requests: merge_requests) }.count
+ control_count = ActiveRecord::QueryRecorder.new { subject.send(:store_dev_info, project: project, merge_requests: merge_requests) }.count
merge_requests << create(:merge_request, :unique_branches)
- expect { subject.store_dev_info(project: project, merge_requests: merge_requests) }.not_to exceed_query_limit(control_count)
+ expect { subject.send(:store_dev_info, project: project, merge_requests: merge_requests) }.not_to exceed_query_limit(control_count)
end
end
end
diff --git a/spec/lib/atlassian/jira_connect/serializers/build_entity_spec.rb b/spec/lib/atlassian/jira_connect/serializers/build_entity_spec.rb
new file mode 100644
index 00000000000..52e475d20ca
--- /dev/null
+++ b/spec/lib/atlassian/jira_connect/serializers/build_entity_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Atlassian::JiraConnect::Serializers::BuildEntity do
+ let_it_be(:user) { create_default(:user) }
+ let_it_be(:project) { create_default(:project) }
+
+ subject { described_class.represent(pipeline) }
+
+ context 'when the pipeline does not belong to any Jira issue' do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ describe '#issue_keys' do
+ it 'is empty' do
+ expect(subject.issue_keys).to be_empty
+ end
+ end
+
+ describe '#to_json' do
+ it 'can encode the object' do
+ expect(subject.to_json).to be_valid_json
+ end
+
+ it 'is invalid, since it has no issue keys' do
+ expect(subject.to_json).not_to be_valid_json.according_to_schema(Atlassian::Schemata.build_info)
+ end
+ end
+ end
+
+ context 'when the pipeline does belong to a Jira issue' do
+ let(:pipeline) { create(:ci_pipeline, merge_request: merge_request) }
+
+ %i[jira_branch jira_title].each do |trait|
+ context "because it belongs to an MR with a #{trait}" do
+ let(:merge_request) { create(:merge_request, trait) }
+
+ describe '#issue_keys' do
+ it 'is not empty' do
+ expect(subject.issue_keys).not_to be_empty
+ end
+ end
+
+ describe '#to_json' do
+ it 'is valid according to the build info schema' do
+ expect(subject.to_json).to be_valid_json.according_to_schema(Atlassian::Schemata.build_info)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
index dbc04704fba..450e396a389 100644
--- a/spec/lib/backup/files_spec.rb
+++ b/spec/lib/backup/files_spec.rb
@@ -149,13 +149,27 @@ RSpec.describe Backup::Files do
end
it 'excludes tmp dirs from rsync' do
- expect(Gitlab::Popen).to receive(:popen).with(%w(rsync -a --exclude=lost+found --exclude=/@pages.tmp /var/gitlab-pages /var/gitlab-backup)).and_return(['', 0])
+ expect(Gitlab::Popen).to receive(:popen)
+ .with(%w(rsync -a --delete --exclude=lost+found --exclude=/@pages.tmp /var/gitlab-pages /var/gitlab-backup))
+ .and_return(['', 0])
subject.dump
end
+ it 'retries if rsync fails due to vanishing files' do
+ expect(Gitlab::Popen).to receive(:popen)
+ .with(%w(rsync -a --delete --exclude=lost+found --exclude=/@pages.tmp /var/gitlab-pages /var/gitlab-backup))
+ .and_return(['rsync failed', 24], ['', 0])
+
+ expect do
+ subject.dump
+ end.to output(/files vanished during rsync, retrying/).to_stdout
+ end
+
it 'raises an error and outputs an error message if rsync failed' do
- allow(Gitlab::Popen).to receive(:popen).with(%w(rsync -a --exclude=lost+found --exclude=/@pages.tmp /var/gitlab-pages /var/gitlab-backup)).and_return(['rsync failed', 1])
+ allow(Gitlab::Popen).to receive(:popen)
+ .with(%w(rsync -a --delete --exclude=lost+found --exclude=/@pages.tmp /var/gitlab-pages /var/gitlab-backup))
+ .and_return(['rsync failed', 1])
expect do
subject.dump
diff --git a/spec/lib/backup/repositories_spec.rb b/spec/lib/backup/repositories_spec.rb
index 9c139e9f954..492058c6a00 100644
--- a/spec/lib/backup/repositories_spec.rb
+++ b/spec/lib/backup/repositories_spec.rb
@@ -242,7 +242,9 @@ RSpec.describe Backup::Repositories do
# 4 times = project repo + wiki repo + project_snippet repo + personal_snippet repo
expect(Repository).to receive(:new).exactly(4).times.and_wrap_original do |method, *original_args|
- repository = method.call(*original_args)
+ full_path, container, kwargs = original_args
+
+ repository = method.call(full_path, container, **kwargs)
expect(repository).to receive(:remove)
diff --git a/spec/lib/banzai/filter/ascii_doc_sanitization_filter_spec.rb b/spec/lib/banzai/filter/ascii_doc_sanitization_filter_spec.rb
new file mode 100644
index 00000000000..272b4386ec8
--- /dev/null
+++ b/spec/lib/banzai/filter/ascii_doc_sanitization_filter_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::AsciiDocSanitizationFilter do
+ include FilterSpecHelper
+
+ it 'preserves footnotes refs' do
+ result = filter('<p>This paragraph has a footnote.<sup>[<a id="_footnoteref_1" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p>').to_html
+ expect(result).to eq('<p>This paragraph has a footnote.<sup>[<a id="_footnoteref_1" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p>')
+ end
+
+ it 'preserves footnotes defs' do
+ result = filter('<div id="_footnotedef_1">
+<a href="#_footnoteref_1">1</a>. This is the text of the footnote.</div>').to_html
+ expect(result).to eq(%(<div id="_footnotedef_1">
+<a href="#_footnoteref_1">1</a>. This is the text of the footnote.</div>))
+ end
+
+ it 'preserves user-content- prefixed ids on anchors' do
+ result = filter('<p><a id="user-content-cross-references"></a>A link to another location within an AsciiDoc document.</p>').to_html
+ expect(result).to eq(%(<p><a id="user-content-cross-references"></a>A link to another location within an AsciiDoc document.</p>))
+ end
+
+ it 'preserves user-content- prefixed ids on div (blocks)' do
+ html_content = <<~HTML
+ <div id="user-content-open-block" class="openblock">
+ <div class="content">
+ <div class="paragraph">
+ <p>This is an open block</p>
+ </div>
+ </div>
+ </div>
+ HTML
+ output = <<~SANITIZED_HTML
+ <div id="user-content-open-block">
+ <div>
+ <div>
+ <p>This is an open block</p>
+ </div>
+ </div>
+ </div>
+ SANITIZED_HTML
+ expect(filter(html_content).to_html).to eq(output)
+ end
+
+ it 'preserves section anchor ids' do
+ result = filter(%(<h2 id="user-content-first-section">
+<a class="anchor" href="#user-content-first-section"></a>First section</h2>)).to_html
+ expect(result).to eq(%(<h2 id="user-content-first-section">
+<a class="anchor" href="#user-content-first-section"></a>First section</h2>))
+ end
+
+ it 'removes non prefixed ids' do
+ result = filter('<p><a id="cross-references"></a>A link to another location within an AsciiDoc document.</p>').to_html
+ expect(result).to eq(%(<p><a></a>A link to another location within an AsciiDoc document.</p>))
+ end
+end
diff --git a/spec/lib/banzai/filter/kroki_filter_spec.rb b/spec/lib/banzai/filter/kroki_filter_spec.rb
new file mode 100644
index 00000000000..57caba1d4d7
--- /dev/null
+++ b/spec/lib/banzai/filter/kroki_filter_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::KrokiFilter do
+ include FilterSpecHelper
+
+ it 'replaces nomnoml pre tag with img tag if kroki is enabled' do
+ stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
+ doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
+
+ expect(doc.to_s).to eq '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==">'
+ end
+
+ it 'replaces nomnoml pre tag with img tag if both kroki and plantuml are enabled' do
+ stub_application_setting(kroki_enabled: true,
+ kroki_url: "http://localhost:8000",
+ plantuml_enabled: true,
+ plantuml_url: "http://localhost:8080")
+ doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
+
+ expect(doc.to_s).to eq '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==">'
+ end
+
+ it 'does not replace nomnoml pre tag with img tag if kroki is disabled' do
+ stub_application_setting(kroki_enabled: false)
+ doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
+
+ expect(doc.to_s).to eq "<pre lang=\"nomnoml\"><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:&gt;[foul mouth]\n]</code></pre>"
+ end
+
+ it 'does not replace plantuml pre tag with img tag if both kroki and plantuml are enabled' do
+ stub_application_setting(kroki_enabled: true,
+ kroki_url: "http://localhost:8000",
+ plantuml_enabled: true,
+ plantuml_url: "http://localhost:8080")
+ doc = filter("<pre lang='plantuml'><code>Bob->Alice : hello</code></pre>")
+
+ expect(doc.to_s).to eq '<pre lang="plantuml"><code>Bob-&gt;Alice : hello</code></pre>'
+ end
+end
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
index 8d01a651651..c5e84a0c1e7 100644
--- a/spec/lib/banzai/filter/markdown_filter_spec.rb
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -46,6 +46,12 @@ RSpec.describe Banzai::Filter::MarkdownFilter do
expect(result).to start_with('<pre><code lang="日">')
end
+
+ it 'works with additional language parameters' do
+ result = filter("```ruby:red gem\nsome code\n```", no_sourcepos: true)
+
+ expect(result).to start_with('<pre><code lang="ruby:red gem">')
+ end
end
end
diff --git a/spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb b/spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb
index d8841a9753e..74005adf673 100644
--- a/spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Banzai::Pipeline::JiraImport::AdfCommonmarkPipeline do
let_it_be(:fixtures_path) { 'lib/kramdown/atlassian_document_format' }
- it 'converts text in Atlassian Document Format ' do
+ it 'converts text in Atlassian Document Format' do
source = fixture_file(File.join(fixtures_path, 'paragraph.json'))
target = fixture_file(File.join(fixtures_path, 'paragraph.md'))
output = described_class.call(source, {})[:output]
diff --git a/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb b/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb
index cde8e2d5c18..a7a19fb73fc 100644
--- a/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb
+++ b/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb
@@ -41,12 +41,11 @@ RSpec.describe BulkImports::Common::Extractors::GraphqlExtractor do
end
context 'when variables are present' do
- let(:query) { { query: double(to_s: 'test', variables: { full_path: :source_full_path }) } }
+ let(:variables) { { foo: :bar } }
+ let(:query) { { query: double(to_s: 'test', variables: variables) } }
it 'builds graphql query variables for import entity' do
- expected_variables = { full_path: import_entity.source_full_path }
-
- expect(graphql_client).to receive(:execute).with(anything, expected_variables)
+ expect(graphql_client).to receive(:execute).with(anything, variables)
subject.extract(context).first
end
diff --git a/spec/lib/bulk_imports/common/transformers/graphql_cleaner_transformer_spec.rb b/spec/lib/bulk_imports/common/transformers/graphql_cleaner_transformer_spec.rb
deleted file mode 100644
index 8f39b6e7c93..00000000000
--- a/spec/lib/bulk_imports/common/transformers/graphql_cleaner_transformer_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe BulkImports::Common::Transformers::GraphqlCleanerTransformer do
- describe '#transform' do
- let_it_be(:expected_output) do
- {
- 'name' => 'test',
- 'fullName' => 'test',
- 'description' => 'test',
- 'labels' => [
- { 'title' => 'label1' },
- { 'title' => 'label2' },
- { 'title' => 'label3' }
- ]
- }
- end
-
- it 'deep cleans hash from GraphQL keys' do
- data = {
- 'data' => {
- 'group' => {
- 'name' => 'test',
- 'fullName' => 'test',
- 'description' => 'test',
- 'labels' => {
- 'edges' => [
- { 'node' => { 'title' => 'label1' } },
- { 'node' => { 'title' => 'label2' } },
- { 'node' => { 'title' => 'label3' } }
- ]
- }
- }
- }
- }
-
- transformed_data = described_class.new.transform(nil, data)
-
- expect(transformed_data).to eq(expected_output)
- end
-
- context 'when data does not have data/group nesting' do
- it 'deep cleans hash from GraphQL keys' do
- data = {
- 'name' => 'test',
- 'fullName' => 'test',
- 'description' => 'test',
- 'labels' => {
- 'edges' => [
- { 'node' => { 'title' => 'label1' } },
- { 'node' => { 'title' => 'label2' } },
- { 'node' => { 'title' => 'label3' } }
- ]
- }
- }
-
- transformed_data = described_class.new.transform(nil, data)
-
- expect(transformed_data).to eq(expected_output)
- end
- end
-
- context 'when data is not a hash' do
- it 'does not perform transformation' do
- data = 'test'
-
- transformed_data = described_class.new.transform(nil, data)
-
- expect(transformed_data).to eq(data)
- end
- end
-
- context 'when nested data is not an array or hash' do
- it 'only removes top level data/group keys' do
- data = {
- 'data' => {
- 'group' => 'test'
- }
- }
-
- transformed_data = described_class.new.transform(nil, data)
-
- expect(transformed_data).to eq('test')
- end
- end
- end
-end
diff --git a/spec/lib/bulk_imports/common/transformers/hash_key_digger_spec.rb b/spec/lib/bulk_imports/common/transformers/hash_key_digger_spec.rb
new file mode 100644
index 00000000000..2b33701653e
--- /dev/null
+++ b/spec/lib/bulk_imports/common/transformers/hash_key_digger_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Common::Transformers::HashKeyDigger do
+ describe '#transform' do
+ it 'when the key_path is an array' do
+ data = { foo: { bar: :value } }
+ key_path = %i[foo bar]
+ transformed = described_class.new(key_path: key_path).transform(nil, data)
+
+ expect(transformed).to eq(:value)
+ end
+
+ it 'when the key_path is not an array' do
+ data = { foo: { bar: :value } }
+ key_path = :foo
+ transformed = described_class.new(key_path: key_path).transform(nil, data)
+
+ expect(transformed).to eq({ bar: :value })
+ end
+
+ it "when the data is not a hash" do
+ expect { described_class.new(key_path: nil).transform(nil, nil) }
+ .to raise_error(ArgumentError, "Given data must be a Hash")
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/common/transformers/prohibited_attributes_transformer_spec.rb b/spec/lib/bulk_imports/common/transformers/prohibited_attributes_transformer_spec.rb
new file mode 100644
index 00000000000..03d138b227c
--- /dev/null
+++ b/spec/lib/bulk_imports/common/transformers/prohibited_attributes_transformer_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Common::Transformers::ProhibitedAttributesTransformer do
+ describe '#transform' do
+ let_it_be(:hash) do
+ {
+ 'id' => 101,
+ 'service_id' => 99,
+ 'moved_to_id' => 99,
+ 'namespace_id' => 99,
+ 'ci_id' => 99,
+ 'random_project_id' => 99,
+ 'random_id' => 99,
+ 'milestone_id' => 99,
+ 'project_id' => 99,
+ 'user_id' => 99,
+ 'random_id_in_the_middle' => 99,
+ 'notid' => 99,
+ 'import_source' => 'test',
+ 'import_type' => 'test',
+ 'non_existent_attr' => 'test',
+ 'some_html' => '<p>dodgy html</p>',
+ 'legit_html' => '<p>legit html</p>',
+ '_html' => '<p>perfectly ordinary html</p>',
+ 'cached_markdown_version' => 12345,
+ 'custom_attributes' => 'test',
+ 'some_attributes_metadata' => 'test',
+ 'group_id' => 99,
+ 'commit_id' => 99,
+ 'issue_ids' => [1, 2, 3],
+ 'merge_request_ids' => [1, 2, 3],
+ 'note_ids' => [1, 2, 3],
+ 'remote_attachment_url' => 'http://something.dodgy',
+ 'remote_attachment_request_header' => 'bad value',
+ 'remote_attachment_urls' => %w(http://something.dodgy http://something.okay),
+ 'attributes' => {
+ 'issue_ids' => [1, 2, 3],
+ 'merge_request_ids' => [1, 2, 3],
+ 'note_ids' => [1, 2, 3]
+ },
+ 'variables_attributes' => {
+ 'id' => 1
+ },
+ 'attr_with_nested_attrs' => {
+ 'nested_id' => 1,
+ 'nested_attr' => 2
+ }
+ }
+ end
+
+ let(:expected_hash) do
+ {
+ 'random_id_in_the_middle' => 99,
+ 'notid' => 99,
+ 'import_source' => 'test',
+ 'import_type' => 'test',
+ 'non_existent_attr' => 'test',
+ 'attr_with_nested_attrs' => {
+ 'nested_attr' => 2
+ }
+ }
+ end
+
+ it 'removes prohibited attributes' do
+ transformed_hash = subject.transform(nil, hash)
+
+ expect(transformed_hash).to eq(expected_hash)
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb
index 3949dd23b49..c9b481388c3 100644
--- a/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb
@@ -72,7 +72,6 @@ RSpec.describe BulkImports::Groups::Pipelines::GroupPipeline do
describe 'pipeline parts' do
it { expect(described_class).to include_module(BulkImports::Pipeline) }
- it { expect(described_class).to include_module(BulkImports::Pipeline::Attributes) }
it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) }
it 'has extractors' do
@@ -90,13 +89,17 @@ RSpec.describe BulkImports::Groups::Pipelines::GroupPipeline do
it 'has transformers' do
expect(described_class.transformers)
.to contain_exactly(
- { klass: BulkImports::Common::Transformers::GraphqlCleanerTransformer, options: nil },
+ { klass: BulkImports::Common::Transformers::HashKeyDigger, options: { key_path: %w[data group] } },
{ klass: BulkImports::Common::Transformers::UnderscorifyKeysTransformer, options: nil },
- { klass: BulkImports::Groups::Transformers::GroupAttributesTransformer, options: nil })
+ { klass: BulkImports::Common::Transformers::ProhibitedAttributesTransformer, options: nil },
+ { klass: BulkImports::Groups::Transformers::GroupAttributesTransformer, options: nil }
+ )
end
it 'has loaders' do
- expect(described_class.loaders).to contain_exactly({ klass: BulkImports::Groups::Loaders::GroupLoader, options: nil })
+ expect(described_class.loaders).to contain_exactly({
+ klass: BulkImports::Groups::Loaders::GroupLoader, options: nil
+ })
end
end
end
diff --git a/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb
index 60a4a796682..788a6e98c45 100644
--- a/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb
@@ -55,7 +55,6 @@ RSpec.describe BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline do
describe 'pipeline parts' do
it { expect(described_class).to include_module(BulkImports::Pipeline) }
- it { expect(described_class).to include_module(BulkImports::Pipeline::Attributes) }
it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) }
it 'has extractors' do
@@ -67,8 +66,8 @@ RSpec.describe BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline do
it 'has transformers' do
expect(described_class.transformers).to contain_exactly(
- klass: BulkImports::Groups::Transformers::SubgroupToEntityTransformer,
- options: nil
+ { klass: BulkImports::Common::Transformers::ProhibitedAttributesTransformer, options: nil },
+ { klass: BulkImports::Groups::Transformers::SubgroupToEntityTransformer, options: nil }
)
end
diff --git a/spec/lib/bulk_imports/importers/group_importer_spec.rb b/spec/lib/bulk_imports/importers/group_importer_spec.rb
index 95ac5925c97..95dca7fc486 100644
--- a/spec/lib/bulk_imports/importers/group_importer_spec.rb
+++ b/spec/lib/bulk_imports/importers/group_importer_spec.rb
@@ -18,12 +18,12 @@ RSpec.describe BulkImports::Importers::GroupImporter do
subject { described_class.new(bulk_import_entity) }
before do
+ allow(Gitlab).to receive(:ee?).and_return(false)
allow(BulkImports::Pipeline::Context).to receive(:new).and_return(context)
- stub_http_requests
end
describe '#execute' do
- it "starts the entity and run its pipelines" do
+ it 'starts the entity and run its pipelines' do
expect(bulk_import_entity).to receive(:start).and_call_original
expect_to_run_pipeline BulkImports::Groups::Pipelines::GroupPipeline, context: context
expect_to_run_pipeline BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline, context: context
@@ -32,6 +32,18 @@ RSpec.describe BulkImports::Importers::GroupImporter do
expect(bulk_import_entity.reload).to be_finished
end
+
+ context 'when failed' do
+ let(:bulk_import_entity) { create(:bulk_import_entity, :failed, bulk_import: bulk_import) }
+
+ it 'does not transition entity to finished state' do
+ allow(bulk_import_entity).to receive(:start!)
+
+ subject.execute
+
+ expect(bulk_import_entity.reload).to be_failed
+ end
+ end
end
def expect_to_run_pipeline(klass, context:)
@@ -39,18 +51,4 @@ RSpec.describe BulkImports::Importers::GroupImporter do
expect(pipeline).to receive(:run).with(context)
end
end
-
- def stub_http_requests
- double_response = double(
- code: 200,
- success?: true,
- parsed_response: {},
- headers: {}
- )
-
- allow_next_instance_of(BulkImports::Clients::Http) do |client|
- allow(client).to receive(:get).and_return(double_response)
- allow(client).to receive(:post).and_return(double_response)
- end
- end
end
diff --git a/spec/lib/bulk_imports/pipeline/runner_spec.rb b/spec/lib/bulk_imports/pipeline/runner_spec.rb
index 8c882c799ec..60833e83dcc 100644
--- a/spec/lib/bulk_imports/pipeline/runner_spec.rb
+++ b/spec/lib/bulk_imports/pipeline/runner_spec.rb
@@ -3,26 +3,32 @@
require 'spec_helper'
RSpec.describe BulkImports::Pipeline::Runner do
- describe 'pipeline runner' do
- before do
- extractor = Class.new do
- def initialize(options = {}); end
+ let(:extractor) do
+ Class.new do
+ def initialize(options = {}); end
- def extract(context); end
- end
+ def extract(context); end
+ end
+ end
- transformer = Class.new do
- def initialize(options = {}); end
+ let(:transformer) do
+ Class.new do
+ def initialize(options = {}); end
- def transform(context, entry); end
- end
+ def transform(context); end
+ end
+ end
- loader = Class.new do
- def initialize(options = {}); end
+ let(:loader) do
+ Class.new do
+ def initialize(options = {}); end
- def load(context, entry); end
- end
+ def load(context); end
+ end
+ end
+ describe 'pipeline runner' do
+ before do
stub_const('BulkImports::Extractor', extractor)
stub_const('BulkImports::Transformer', transformer)
stub_const('BulkImports::Loader', loader)
@@ -38,37 +44,126 @@ RSpec.describe BulkImports::Pipeline::Runner do
stub_const('BulkImports::MyPipeline', pipeline)
end
- it 'runs pipeline extractor, transformer, loader' do
- context = instance_double(
- BulkImports::Pipeline::Context,
- entity: instance_double(BulkImports::Entity, id: 1, source_type: 'group')
- )
- entries = [{ foo: :bar }]
-
- expect_next_instance_of(BulkImports::Extractor) do |extractor|
- expect(extractor).to receive(:extract).with(context).and_return(entries)
+ context 'when entity is not marked as failed' do
+ let(:context) do
+ instance_double(
+ BulkImports::Pipeline::Context,
+ entity: instance_double(BulkImports::Entity, id: 1, source_type: 'group', failed?: false)
+ )
end
- expect_next_instance_of(BulkImports::Transformer) do |transformer|
- expect(transformer).to receive(:transform).with(context, entries.first).and_return(entries.first)
+ it 'runs pipeline extractor, transformer, loader' do
+ entries = [{ foo: :bar }]
+
+ expect_next_instance_of(BulkImports::Extractor) do |extractor|
+ expect(extractor).to receive(:extract).with(context).and_return(entries)
+ end
+
+ expect_next_instance_of(BulkImports::Transformer) do |transformer|
+ expect(transformer).to receive(:transform).with(context, entries.first).and_return(entries.first)
+ end
+
+ expect_next_instance_of(BulkImports::Loader) do |loader|
+ expect(loader).to receive(:load).with(context, entries.first)
+ end
+
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger).to receive(:info)
+ .with(
+ message: 'Pipeline started',
+ pipeline_class: 'BulkImports::MyPipeline',
+ bulk_import_entity_id: 1,
+ bulk_import_entity_type: 'group'
+ )
+ expect(logger).to receive(:info)
+ .with(bulk_import_entity_id: 1, bulk_import_entity_type: 'group', extractor: 'BulkImports::Extractor')
+ expect(logger).to receive(:info)
+ .with(bulk_import_entity_id: 1, bulk_import_entity_type: 'group', transformer: 'BulkImports::Transformer')
+ expect(logger).to receive(:info)
+ .with(bulk_import_entity_id: 1, bulk_import_entity_type: 'group', loader: 'BulkImports::Loader')
+ end
+
+ BulkImports::MyPipeline.new.run(context)
end
- expect_next_instance_of(BulkImports::Loader) do |loader|
- expect(loader).to receive(:load).with(context, entries.first)
+ context 'when exception is raised' do
+ let(:entity) { create(:bulk_import_entity, :created) }
+ let(:context) { BulkImports::Pipeline::Context.new(entity: entity) }
+
+ before do
+ allow_next_instance_of(BulkImports::Extractor) do |extractor|
+ allow(extractor).to receive(:extract).with(context).and_raise(StandardError, 'Error!')
+ end
+ end
+
+ it 'logs import failure' do
+ BulkImports::MyPipeline.new.run(context)
+
+ failure = entity.failures.first
+
+ expect(failure).to be_present
+ expect(failure.pipeline_class).to eq('BulkImports::MyPipeline')
+ expect(failure.exception_class).to eq('StandardError')
+ expect(failure.exception_message).to eq('Error!')
+ end
+
+ context 'when pipeline is marked to abort on failure' do
+ before do
+ BulkImports::MyPipeline.abort_on_failure!
+ end
+
+ it 'marks entity as failed' do
+ BulkImports::MyPipeline.new.run(context)
+
+ expect(entity.failed?).to eq(true)
+ end
+
+ it 'logs warn message' do
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger).to receive(:warn)
+ .with(
+ message: 'Pipeline failed',
+ pipeline_class: 'BulkImports::MyPipeline',
+ bulk_import_entity_id: entity.id,
+ bulk_import_entity_type: entity.source_type
+ )
+ end
+
+ BulkImports::MyPipeline.new.run(context)
+ end
+ end
+
+ context 'when pipeline is not marked to abort on failure' do
+ it 'marks entity as failed' do
+ BulkImports::MyPipeline.new.run(context)
+
+ expect(entity.failed?).to eq(false)
+ end
+ end
end
+ end
- expect_next_instance_of(Gitlab::Import::Logger) do |logger|
- expect(logger).to receive(:info)
- .with(message: "Pipeline started", pipeline: 'BulkImports::MyPipeline', entity: 1, entity_type: 'group')
- expect(logger).to receive(:info)
- .with(entity: 1, entity_type: 'group', extractor: 'BulkImports::Extractor')
- expect(logger).to receive(:info)
- .with(entity: 1, entity_type: 'group', transformer: 'BulkImports::Transformer')
- expect(logger).to receive(:info)
- .with(entity: 1, entity_type: 'group', loader: 'BulkImports::Loader')
+ context 'when entity is marked as failed' do
+ let(:context) do
+ instance_double(
+ BulkImports::Pipeline::Context,
+ entity: instance_double(BulkImports::Entity, id: 1, source_type: 'group', failed?: true)
+ )
end
- BulkImports::MyPipeline.new.run(context)
+ it 'logs and returns without execution' do
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger).to receive(:info)
+ .with(
+ message: 'Skipping due to failed pipeline status',
+ pipeline_class: 'BulkImports::MyPipeline',
+ bulk_import_entity_id: 1,
+ bulk_import_entity_type: 'group'
+ )
+ end
+
+ BulkImports::MyPipeline.new.run(context)
+ end
end
end
end
diff --git a/spec/lib/bulk_imports/pipeline/attributes_spec.rb b/spec/lib/bulk_imports/pipeline_spec.rb
index 54c5dbd4cae..94052be7df2 100644
--- a/spec/lib/bulk_imports/pipeline/attributes_spec.rb
+++ b/spec/lib/bulk_imports/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe BulkImports::Pipeline::Attributes do
+RSpec.describe BulkImports::Pipeline do
describe 'pipeline attributes' do
before do
stub_const('BulkImports::Extractor', Class.new)
@@ -10,7 +10,9 @@ RSpec.describe BulkImports::Pipeline::Attributes do
stub_const('BulkImports::Loader', Class.new)
klass = Class.new do
- include BulkImports::Pipeline::Attributes
+ include BulkImports::Pipeline
+
+ abort_on_failure!
extractor BulkImports::Extractor, { foo: :bar }
transformer BulkImports::Transformer, { foo: :bar }
@@ -25,6 +27,7 @@ RSpec.describe BulkImports::Pipeline::Attributes do
expect(BulkImports::MyPipeline.extractors).to contain_exactly({ klass: BulkImports::Extractor, options: { foo: :bar } })
expect(BulkImports::MyPipeline.transformers).to contain_exactly({ klass: BulkImports::Transformer, options: { foo: :bar } })
expect(BulkImports::MyPipeline.loaders).to contain_exactly({ klass: BulkImports::Loader, options: { foo: :bar } })
+ expect(BulkImports::MyPipeline.abort_on_failure?).to eq(true)
end
end
@@ -36,6 +39,7 @@ RSpec.describe BulkImports::Pipeline::Attributes do
BulkImports::MyPipeline.extractor(klass, options)
BulkImports::MyPipeline.transformer(klass, options)
BulkImports::MyPipeline.loader(klass, options)
+ BulkImports::MyPipeline.abort_on_failure!
expect(BulkImports::MyPipeline.extractors)
.to contain_exactly(
@@ -51,6 +55,8 @@ RSpec.describe BulkImports::Pipeline::Attributes do
.to contain_exactly(
{ klass: BulkImports::Loader, options: { foo: :bar } },
{ klass: klass, options: options })
+
+ expect(BulkImports::MyPipeline.abort_on_failure?).to eq(true)
end
end
end
diff --git a/spec/lib/feature/definition_spec.rb b/spec/lib/feature/definition_spec.rb
index fa0207d829a..21120012927 100644
--- a/spec/lib/feature/definition_spec.rb
+++ b/spec/lib/feature/definition_spec.rb
@@ -64,6 +64,11 @@ RSpec.describe Feature::Definition do
expect { definition.valid_usage!(type_in_code: :development, default_enabled_in_code: false) }
.to raise_error(/The `default_enabled:` of `feature_flag` is not equal to config/)
end
+
+ it 'allows passing `default_enabled: :yaml`' do
+ expect { definition.valid_usage!(type_in_code: :development, default_enabled_in_code: :yaml) }
+ .not_to raise_error
+ end
end
end
@@ -75,7 +80,7 @@ RSpec.describe Feature::Definition do
describe '.load_from_file' do
it 'properly loads a definition from file' do
- expect(File).to receive(:read).with(path) { yaml_content }
+ expect_file_read(path, content: yaml_content)
expect(described_class.send(:load_from_file, path).attributes)
.to eq(definition.attributes)
@@ -93,7 +98,7 @@ RSpec.describe Feature::Definition do
context 'for invalid definition' do
it 'raises exception' do
- expect(File).to receive(:read).with(path) { '{}' }
+ expect_file_read(path, content: '{}')
expect do
described_class.send(:load_from_file, path)
@@ -209,4 +214,58 @@ RSpec.describe Feature::Definition do
end
end
end
+
+ describe '.defaul_enabled?' do
+ subject { described_class.default_enabled?(key) }
+
+ context 'when feature flag exist' do
+ let(:key) { definition.key }
+
+ before do
+ allow(described_class).to receive(:definitions) do
+ { definition.key => definition }
+ end
+ end
+
+ context 'when default_enabled is true' do
+ it 'returns the value from the definition' do
+ expect(subject).to eq(true)
+ end
+ end
+
+ context 'when default_enabled is false' do
+ let(:attributes) do
+ { name: 'feature_flag',
+ type: 'development',
+ default_enabled: false }
+ end
+
+ it 'returns the value from the definition' do
+ expect(subject).to eq(false)
+ end
+ end
+ end
+
+ context 'when feature flag does not exist' do
+ let(:key) { :unknown_feature_flag }
+
+ context 'when on dev or test environment' do
+ it 'raises an error' do
+ expect { subject }.to raise_error(
+ Feature::InvalidFeatureFlagError,
+ "The feature flag YAML definition for 'unknown_feature_flag' does not exist")
+ end
+ end
+
+ context 'when on production environment' do
+ before do
+ allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false)
+ end
+
+ it 'returns false' do
+ expect(subject).to eq(false)
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 5dff9dbd995..1bcb2223012 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -249,10 +249,12 @@ RSpec.describe Feature, stub_feature_flags: false do
Feature::Definition.new('development/my_feature_flag.yml',
name: 'my_feature_flag',
type: 'development',
- default_enabled: false
+ default_enabled: default_enabled
).tap(&:validate!)
end
+ let(:default_enabled) { false }
+
before do
stub_env('LAZILY_CREATE_FEATURE_FLAG', '0')
@@ -275,6 +277,63 @@ RSpec.describe Feature, stub_feature_flags: false do
expect { described_class.enabled?(:my_feature_flag, default_enabled: true) }
.to raise_error(/The `default_enabled:` of/)
end
+
+ context 'when `default_enabled: :yaml` is used in code' do
+ it 'reads the default from the YAML definition' do
+ expect(described_class.enabled?(:my_feature_flag, default_enabled: :yaml)).to eq(false)
+ end
+
+ context 'when default_enabled is true in the YAML definition' do
+ let(:default_enabled) { true }
+
+ it 'reads the default from the YAML definition' do
+ expect(described_class.enabled?(:my_feature_flag, default_enabled: :yaml)).to eq(true)
+ end
+ end
+
+ context 'when YAML definition does not exist for an optional type' do
+ let(:optional_type) { described_class::Shared::TYPES.find { |name, attrs| attrs[:optional] }.first }
+
+ context 'when in dev or test environment' do
+ it 'raises an error for dev' do
+ expect { described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml) }
+ .to raise_error(
+ Feature::InvalidFeatureFlagError,
+ "The feature flag YAML definition for 'non_existent_flag' does not exist")
+ end
+ end
+
+ context 'when in production' do
+ before do
+ allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false)
+ end
+
+ context 'when database exists' do
+ before do
+ allow(Gitlab::Database).to receive(:exists?).and_return(true)
+ end
+
+ it 'checks the persisted status and returns false' do
+ expect(described_class).to receive(:get).with(:non_existent_flag).and_call_original
+
+ expect(described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml)).to eq(false)
+ end
+ end
+
+ context 'when database does not exist' do
+ before do
+ allow(Gitlab::Database).to receive(:exists?).and_return(false)
+ end
+
+ it 'returns false without checking the status in the database' do
+ expect(described_class).not_to receive(:get)
+
+ expect(described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml)).to eq(false)
+ end
+ end
+ end
+ end
+ end
end
end
@@ -300,7 +359,119 @@ RSpec.describe Feature, stub_feature_flags: false do
end
end
+ shared_examples_for 'logging' do
+ let(:expected_action) { }
+ let(:expected_extra) { }
+
+ it 'logs the event' do
+ expect(Feature.logger).to receive(:info).with(key: key, action: expected_action, **expected_extra)
+
+ subject
+ end
+ end
+
+ describe '.enable' do
+ subject { described_class.enable(key, thing) }
+
+ let(:key) { :awesome_feature }
+ let(:thing) { true }
+
+ it_behaves_like 'logging' do
+ let(:expected_action) { :enable }
+ let(:expected_extra) { { "extra.thing" => "true" } }
+ end
+
+ context 'when thing is an actor' do
+ let(:thing) { create(:project) }
+
+ it_behaves_like 'logging' do
+ let(:expected_action) { :enable }
+ let(:expected_extra) { { "extra.thing" => "#{thing.flipper_id}" } }
+ end
+ end
+ end
+
+ describe '.disable' do
+ subject { described_class.disable(key, thing) }
+
+ let(:key) { :awesome_feature }
+ let(:thing) { false }
+
+ it_behaves_like 'logging' do
+ let(:expected_action) { :disable }
+ let(:expected_extra) { { "extra.thing" => "false" } }
+ end
+
+ context 'when thing is an actor' do
+ let(:thing) { create(:project) }
+
+ it_behaves_like 'logging' do
+ let(:expected_action) { :disable }
+ let(:expected_extra) { { "extra.thing" => "#{thing.flipper_id}" } }
+ end
+ end
+ end
+
+ describe '.enable_percentage_of_time' do
+ subject { described_class.enable_percentage_of_time(key, percentage) }
+
+ let(:key) { :awesome_feature }
+ let(:percentage) { 50 }
+
+ it_behaves_like 'logging' do
+ let(:expected_action) { :enable_percentage_of_time }
+ let(:expected_extra) { { "extra.percentage" => "#{percentage}" } }
+ end
+ end
+
+ describe '.disable_percentage_of_time' do
+ subject { described_class.disable_percentage_of_time(key) }
+
+ let(:key) { :awesome_feature }
+
+ it_behaves_like 'logging' do
+ let(:expected_action) { :disable_percentage_of_time }
+ let(:expected_extra) { {} }
+ end
+ end
+
+ describe '.enable_percentage_of_actors' do
+ subject { described_class.enable_percentage_of_actors(key, percentage) }
+
+ let(:key) { :awesome_feature }
+ let(:percentage) { 50 }
+
+ it_behaves_like 'logging' do
+ let(:expected_action) { :enable_percentage_of_actors }
+ let(:expected_extra) { { "extra.percentage" => "#{percentage}" } }
+ end
+ end
+
+ describe '.disable_percentage_of_actors' do
+ subject { described_class.disable_percentage_of_actors(key) }
+
+ let(:key) { :awesome_feature }
+
+ it_behaves_like 'logging' do
+ let(:expected_action) { :disable_percentage_of_actors }
+ let(:expected_extra) { {} }
+ end
+ end
+
describe '.remove' do
+ subject { described_class.remove(key) }
+
+ let(:key) { :awesome_feature }
+
+ before do
+ described_class.enable(key)
+ end
+
+ it_behaves_like 'logging' do
+ let(:expected_action) { :remove }
+ let(:expected_extra) { {} }
+ end
+
context 'for a non-persisted feature' do
it 'returns nil' do
expect(described_class.remove(:non_persisted_feature_flag)).to be_nil
diff --git a/spec/lib/gitlab/anonymous_session_spec.rb b/spec/lib/gitlab/anonymous_session_spec.rb
index 671d452ad13..245ca02e91a 100644
--- a/spec/lib/gitlab/anonymous_session_spec.rb
+++ b/spec/lib/gitlab/anonymous_session_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
end
it 'adds expiration time to key' do
- Timecop.freeze do
+ freeze_time do
subject.count_session_ip
Gitlab::Redis::SharedState.with do |redis|
diff --git a/spec/lib/gitlab/asciidoc/html5_converter_spec.rb b/spec/lib/gitlab/asciidoc/html5_converter_spec.rb
new file mode 100644
index 00000000000..84c2cda496e
--- /dev/null
+++ b/spec/lib/gitlab/asciidoc/html5_converter_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Asciidoc::Html5Converter do
+ describe 'convert AsciiDoc to HTML5' do
+ it 'appends user-content- prefix on ref (anchor)' do
+ doc = Asciidoctor::Document.new('')
+ anchor = Asciidoctor::Inline.new(doc, :anchor, '', type: :ref, id: 'cross-references')
+ converter = Gitlab::Asciidoc::Html5Converter.new('gitlab_html5')
+ html = converter.convert_inline_anchor(anchor)
+ expect(html).to eq('<a id="user-content-cross-references"></a>')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 6b93634690c..36e4decdead 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -20,7 +20,7 @@ module Gitlab
expected_asciidoc_opts = {
safe: :secure,
backend: :gitlab_html5,
- attributes: described_class::DEFAULT_ADOC_ATTRS,
+ attributes: described_class::DEFAULT_ADOC_ATTRS.merge({ "kroki-server-url" => nil }),
extensions: be_a(Proc)
}
@@ -35,7 +35,7 @@ module Gitlab
expected_asciidoc_opts = {
safe: :secure,
backend: :gitlab_html5,
- attributes: described_class::DEFAULT_ADOC_ATTRS,
+ attributes: described_class::DEFAULT_ADOC_ATTRS.merge({ "kroki-server-url" => nil }),
extensions: be_a(Proc)
}
@@ -252,6 +252,27 @@ module Gitlab
end
end
+ context 'with xrefs' do
+ it 'preserves ids' do
+ input = <<~ADOC
+ Learn how to xref:cross-references[use cross references].
+
+ [[cross-references]]A link to another location within an AsciiDoc document or between AsciiDoc documents is called a cross reference (also referred to as an xref).
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <p>Learn how to <a href="#cross-references">use cross references</a>.</p>
+ </div>
+ <div>
+ <p><a id="user-content-cross-references"></a>A link to another location within an AsciiDoc document or between AsciiDoc documents is called a cross reference (also referred to as an xref).</p>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
context 'with checklist' do
it 'preserves classes' do
input = <<~ADOC
@@ -462,6 +483,34 @@ module Gitlab
expect(render(input, context)).to include(output.strip)
end
end
+
+ context 'with Kroki enabled' do
+ before do
+ allow_any_instance_of(ApplicationSetting).to receive(:kroki_enabled).and_return(true)
+ allow_any_instance_of(ApplicationSetting).to receive(:kroki_url).and_return('https://kroki.io')
+ end
+
+ it 'converts a graphviz diagram to image' do
+ input = <<~ADOC
+ [graphviz]
+ ....
+ digraph G {
+ Hello->World
+ }
+ ....
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <div>
+ <a class="no-attachment-icon" href="https://kroki.io/graphviz/svg/eNpLyUwvSizIUHBXqOZSUPBIzcnJ17ULzy_KSeGqBQCEzQka" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Diagram" class="lazy" data-src="https://kroki.io/graphviz/svg/eNpLyUwvSizIUHBXqOZSUPBIzcnJ17ULzy_KSeGqBQCEzQka"></a>
+ </div>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
end
context 'with project' do
diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb
index 3c19ef0bd1b..f927d5912bb 100644
--- a/spec/lib/gitlab/auth/auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/auth_finders_spec.rb
@@ -147,6 +147,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do
expect(find_user_from_feed_token(:rss)).to eq user
end
+ it 'returns nil if valid feed_token and disabled' do
+ allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true)
+ set_param(:feed_token, user.feed_token)
+
+ expect(find_user_from_feed_token(:rss)).to be_nil
+ end
+
it 'returns nil if feed_token is blank' do
expect(find_user_from_feed_token(:rss)).to be_nil
end
@@ -377,6 +384,16 @@ RSpec.describe Gitlab::Auth::AuthFinders do
expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
+
+ context 'when using a non-prefixed access token' do
+ let(:personal_access_token) { create(:personal_access_token, :no_prefix, user: user) }
+
+ it 'returns user' do
+ set_header('HTTP_AUTHORIZATION', "Bearer #{personal_access_token.token}")
+
+ expect(find_user_from_access_token).to eq user
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/auth/crowd/authentication_spec.rb b/spec/lib/gitlab/auth/crowd/authentication_spec.rb
new file mode 100644
index 00000000000..71eb8036fdd
--- /dev/null
+++ b/spec/lib/gitlab/auth/crowd/authentication_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Auth::Crowd::Authentication do
+ let(:provider) { 'crowd' }
+ let(:login) { generate(:username) }
+ let(:password) { 'password' }
+ let(:crowd_auth) { described_class.new(provider) }
+ let(:user_info) { { user: login } }
+
+ describe 'login' do
+ before do
+ allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).with(provider).and_return(true)
+ allow(crowd_auth).to receive(:user_info_from_authentication).and_return(user_info)
+ end
+
+ it "finds the user if authentication is successful" do
+ create(:omniauth_user, extern_uid: login, username: login, provider: provider)
+
+ expect(crowd_auth.login(login, password)).to be_truthy
+ end
+
+ it "is false if the user does not exist" do
+ expect(crowd_auth.login(login, password)).to be_falsey
+ end
+
+ it "is false if the authentication fails" do
+ allow(crowd_auth).to receive(:user_info_from_authentication).and_return(nil)
+
+ expect(crowd_auth.login(login, password)).to be_falsey
+ end
+
+ it "fails when crowd is disabled" do
+ allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).with('crowd').and_return(false)
+
+ expect(crowd_auth.login(login, password)).to be_falsey
+ end
+
+ it "fails if no login is supplied" do
+ expect(crowd_auth.login('', password)).to be_falsey
+ end
+
+ it "fails if no password is supplied" do
+ expect(crowd_auth.login(login, '')).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb
index ccaed94b5c8..e910ac09448 100644
--- a/spec/lib/gitlab/auth/ldap/user_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/user_spec.rb
@@ -49,23 +49,6 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
end
- describe '.find_by_uid_and_provider' do
- let(:dn) { 'CN=John Åström, CN=Users, DC=Example, DC=com' }
-
- it 'retrieves the correct user' do
- special_info = {
- name: 'John Åström',
- email: 'john@example.com',
- nickname: 'jastrom'
- }
- special_hash = OmniAuth::AuthHash.new(uid: dn, provider: 'ldapmain', info: special_info)
- special_chars_user = described_class.new(special_hash)
- user = special_chars_user.save
-
- expect(described_class.find_by_uid_and_provider(dn, 'ldapmain')).to eq user
- end
- end
-
describe 'find or create' do
it "finds the user if already existing" do
create(:omniauth_user, extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain')
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 243d0a4cb45..6c6cee9c273 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -25,6 +25,23 @@ RSpec.describe Gitlab::Auth::OAuth::User do
let(:ldap_user) { Gitlab::Auth::Ldap::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
+ describe '.find_by_uid_and_provider' do
+ let(:dn) { 'CN=John Åström, CN=Users, DC=Example, DC=com' }
+
+ it 'retrieves the correct user' do
+ special_info = {
+ name: 'John Åström',
+ email: 'john@example.com',
+ nickname: 'jastrom'
+ }
+ special_hash = OmniAuth::AuthHash.new(uid: dn, provider: 'ldapmain', info: special_info)
+ special_chars_user = described_class.new(special_hash)
+ user = special_chars_user.save
+
+ expect(described_class.find_by_uid_and_provider(dn, 'ldapmain')).to eq user
+ end
+ end
+
describe '#persisted?' do
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
diff --git a/spec/lib/gitlab/auth/otp/session_enforcer_spec.rb b/spec/lib/gitlab/auth/otp/session_enforcer_spec.rb
new file mode 100644
index 00000000000..928aade4008
--- /dev/null
+++ b/spec/lib/gitlab/auth/otp/session_enforcer_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Auth::Otp::SessionEnforcer, :clean_gitlab_redis_shared_state do
+ let_it_be(:key) { create(:key)}
+
+ describe '#update_session' do
+ it 'registers a session in Redis' do
+ redis = double(:redis)
+ expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
+
+ expect(redis).to(
+ receive(:setex)
+ .with("#{described_class::OTP_SESSIONS_NAMESPACE}:#{key.id}",
+ described_class::DEFAULT_EXPIRATION,
+ true)
+ .once)
+
+ described_class.new(key).update_session
+ end
+ end
+
+ describe '#access_restricted?' do
+ subject { described_class.new(key).access_restricted? }
+
+ context 'with existing session' do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set("#{described_class::OTP_SESSIONS_NAMESPACE}:#{key.id}", true )
+ end
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'without an existing session' do
+ it { is_expected.to be_truthy }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb b/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb
index 18fd6d08057..88a245b6b10 100644
--- a/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb
@@ -12,30 +12,32 @@ RSpec.describe Gitlab::Auth::Otp::Strategies::FortiAuthenticator do
let(:api_token) { 's3cr3t' }
let(:forti_authenticator_auth_url) { "https://#{host}:#{port}/api/v1/auth/" }
+ let(:response_status) { 200 }
subject(:validate) { described_class.new(user).validate(otp_code) }
before do
- stub_feature_flags(forti_authenticator: true)
+ stub_feature_flags(forti_authenticator: user)
stub_forti_authenticator_config(
+ enabled: true,
host: host,
port: port,
username: api_username,
- token: api_token
+ access_token: api_token
)
request_body = { username: user.username,
token_code: otp_code }
stub_request(:post, forti_authenticator_auth_url)
- .with(body: JSON(request_body), headers: { 'Content-Type' => 'application/json' })
- .to_return(status: response_status, body: '', headers: {})
+ .with(body: JSON(request_body),
+ headers: { 'Content-Type': 'application/json' },
+ basic_auth: [api_username, api_token])
+ .to_return(status: response_status, body: '')
end
context 'successful validation' do
- let(:response_status) { 200 }
-
it 'returns success' do
expect(validate[:status]).to eq(:success)
end
@@ -49,6 +51,16 @@ RSpec.describe Gitlab::Auth::Otp::Strategies::FortiAuthenticator do
end
end
+ context 'unexpected error' do
+ it 'returns error' do
+ error_message = 'boom!'
+ stub_request(:post, forti_authenticator_auth_url).to_raise(StandardError.new(error_message))
+
+ expect(validate[:status]).to eq(:error)
+ expect(validate[:message]).to eq(error_message)
+ end
+ end
+
def stub_forti_authenticator_config(forti_authenticator_settings)
allow(::Gitlab.config.forti_authenticator).to(receive_messages(forti_authenticator_settings))
end
diff --git a/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb b/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb
new file mode 100644
index 00000000000..1580fc82279
--- /dev/null
+++ b/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Auth::Otp::Strategies::FortiTokenCloud do
+ let_it_be(:user) { create(:user) }
+ let(:otp_code) { 42 }
+
+ let(:url) { 'https://ftc.example.com:9696/api/v1' }
+ let(:client_id) { 'client_id' }
+ let(:client_secret) { 's3cr3t' }
+ let(:access_token_create_url) { url + '/login' }
+ let(:otp_verification_url) { url + '/auth' }
+ let(:access_token) { 'an_access_token' }
+ let(:access_token_create_response_body) { '' }
+
+ subject(:validate) { described_class.new(user).validate(otp_code) }
+
+ before do
+ stub_feature_flags(forti_token_cloud: user)
+
+ stub_const("#{described_class}::BASE_API_URL", url)
+
+ stub_forti_token_cloud_config(
+ enabled: true,
+ client_id: client_id,
+ client_secret: client_secret
+ )
+
+ access_token_request_body = { client_id: client_id,
+ client_secret: client_secret }
+
+ stub_request(:post, access_token_create_url)
+ .with(body: JSON(access_token_request_body), headers: { 'Content-Type' => 'application/json' })
+ .to_return(
+ status: access_token_create_response_status,
+ body: Gitlab::Json.generate(access_token_create_response_body),
+ headers: {}
+ )
+ end
+
+ context 'access token is created successfully' do
+ let(:access_token_create_response_body) { { access_token: access_token, expires_in: 3600 } }
+ let(:access_token_create_response_status) { 201 }
+
+ before do
+ otp_verification_request_body = { username: user.username,
+ token: otp_code }
+
+ stub_request(:post, otp_verification_url)
+ .with(body: JSON(otp_verification_request_body),
+ headers: {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Bearer #{access_token}"
+ })
+ .to_return(status: otp_verification_response_status, body: '', headers: {})
+ end
+
+ context 'otp verification is successful' do
+ let(:otp_verification_response_status) { 200 }
+
+ it 'returns success' do
+ expect(validate[:status]).to eq(:success)
+ end
+ end
+
+ context 'otp verification is not successful' do
+ let(:otp_verification_response_status) { 401 }
+
+ it 'returns error' do
+ expect(validate[:status]).to eq(:error)
+ end
+ end
+ end
+
+ context 'access token creation fails' do
+ let(:access_token_create_response_status) { 400 }
+
+ it 'returns error' do
+ expect(validate[:status]).to eq(:error)
+ end
+ end
+
+ def stub_forti_token_cloud_config(forti_token_cloud_settings)
+ allow(::Gitlab.config.forti_token_cloud).to(receive_messages(forti_token_cloud_settings))
+ end
+end
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index b89ceb37076..ef6b1d72712 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -50,13 +50,13 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token).and_return(access_token_user)
allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
- expect(subject.find_sessionless_user([:api])).to eq access_token_user
+ expect(subject.find_sessionless_user(:api)).to eq access_token_user
end
it 'returns feed_token user if no access_token user found' do
allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
- expect(subject.find_sessionless_user([:api])).to eq feed_token_user
+ expect(subject.find_sessionless_user(:api)).to eq feed_token_user
end
it 'returns static_object_token user if no feed_token user found' do
@@ -64,7 +64,7 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
.to receive(:find_user_from_static_object_token)
.and_return(static_object_token_user)
- expect(subject.find_sessionless_user([:api])).to eq static_object_token_user
+ expect(subject.find_sessionless_user(:api)).to eq static_object_token_user
end
it 'returns job_token user if no static_object_token user found' do
@@ -72,17 +72,61 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
.to receive(:find_user_from_job_token)
.and_return(job_token_user)
- expect(subject.find_sessionless_user([:api])).to eq job_token_user
+ expect(subject.find_sessionless_user(:api)).to eq job_token_user
end
it 'returns nil if no user found' do
- expect(subject.find_sessionless_user([:api])).to be_blank
+ expect(subject.find_sessionless_user(:api)).to be_blank
end
it 'rescue Gitlab::Auth::AuthenticationError exceptions' do
allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token).and_raise(Gitlab::Auth::UnauthorizedError)
- expect(subject.find_sessionless_user([:api])).to be_blank
+ expect(subject.find_sessionless_user(:api)).to be_blank
+ end
+ end
+
+ describe '#find_personal_access_token_from_http_basic_auth' do
+ let_it_be(:personal_access_token) { create(:personal_access_token) }
+ let_it_be(:user) { personal_access_token.user }
+
+ before do
+ allow(subject).to receive(:has_basic_credentials?).and_return(true)
+ allow(subject).to receive(:user_name_and_password).and_return([user.username, personal_access_token.token])
+ end
+
+ context 'with API requests' do
+ before do
+ env['SCRIPT_NAME'] = '/api/endpoint'
+ end
+
+ it 'tries to find the user' do
+ expect(subject.user([:api])).to eq user
+ end
+
+ it 'returns nil if the token is revoked' do
+ personal_access_token.revoke!
+
+ expect(subject.user([:api])).to be_blank
+ end
+
+ it 'returns nil if the token does not have API scope' do
+ personal_access_token.update!(scopes: ['read_registry'])
+
+ expect(subject.user([:api])).to be_blank
+ end
+ end
+
+ context 'without API requests' do
+ before do
+ env['SCRIPT_NAME'] = '/web/endpoint'
+ end
+
+ it 'does not search for job users' do
+ expect(PersonalAccessToken).not_to receive(:find_by_token)
+
+ expect(subject.user([:api])).to be_nil
+ end
end
end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 1768ab41a71..dfd21983682 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -364,20 +364,33 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
let_it_be(:project_access_token) { create(:personal_access_token, user: project_bot_user) }
context 'with valid project access token' do
- before_all do
+ before do
project.add_maintainer(project_bot_user)
end
- it 'succeeds' do
+ it 'successfully authenticates the project bot' do
expect(gl_auth.find_for_git_client(project_bot_user.username, project_access_token.token, project: project, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(project_bot_user, nil, :personal_access_token, described_class.full_authentication_abilities))
end
end
context 'with invalid project access token' do
- it 'fails' do
- expect(gl_auth.find_for_git_client(project_bot_user.username, project_access_token.token, project: project, ip: 'ip'))
- .to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
+ context 'when project bot is not a project member' do
+ it 'fails for a non-project member' do
+ expect(gl_auth.find_for_git_client(project_bot_user.username, project_access_token.token, project: project, ip: 'ip'))
+ .to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
+ end
+ end
+
+ context 'when project bot user is blocked' do
+ before do
+ project_bot_user.block!
+ end
+
+ it 'fails for a blocked project bot' do
+ expect(gl_auth.find_for_git_client(project_bot_user.username, project_access_token.token, project: project, ip: 'ip'))
+ .to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
+ end
end
end
end
diff --git a/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb b/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb
index 2be9c03e5bd..54c14e7a4b8 100644
--- a/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb
@@ -9,10 +9,10 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillDeploymentClustersFromDeploy
it 'backfills deployment_cluster for all deployments in the given range with a non-null cluster_id' do
deployment_clusters = table(:deployment_clusters)
- namespace = table(:namespaces).create(name: 'the-namespace', path: 'the-path')
- project = table(:projects).create(name: 'the-project', namespace_id: namespace.id)
- environment = table(:environments).create(name: 'the-environment', project_id: project.id, slug: 'slug')
- cluster = table(:clusters).create(name: 'the-cluster')
+ namespace = table(:namespaces).create!(name: 'the-namespace', path: 'the-path')
+ project = table(:projects).create!(name: 'the-project', namespace_id: namespace.id)
+ environment = table(:environments).create!(name: 'the-environment', project_id: project.id, slug: 'slug')
+ cluster = table(:clusters).create!(name: 'the-cluster')
deployment_data = { cluster_id: cluster.id, project_id: project.id, environment_id: environment.id, ref: 'abc', tag: false, sha: 'sha', status: 1 }
expected_deployment_1 = create_deployment(**deployment_data)
@@ -21,7 +21,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillDeploymentClustersFromDeploy
out_of_range_deployment = create_deployment(**deployment_data, cluster_id: cluster.id) # expected to be out of range
# to test "ON CONFLICT DO NOTHING"
- existing_record_for_deployment_2 = deployment_clusters.create(
+ existing_record_for_deployment_2 = deployment_clusters.create!(
deployment_id: expected_deployment_2.id,
cluster_id: expected_deployment_2.cluster_id,
kubernetes_namespace: 'production'
@@ -38,7 +38,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillDeploymentClustersFromDeploy
def create_deployment(**data)
@iid ||= 0
@iid += 1
- table(:deployments).create(iid: @iid, **data)
+ table(:deployments).create!(iid: @iid, **data)
end
end
end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
index 8a8edc1af29..539dff86168 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
@@ -81,7 +81,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
end
it 'returns the correct disk_path using the route entry' do
- project_legacy_storage_5.route.update(path: 'zoo/new-test')
+ project_legacy_storage_5.route.update!(path: 'zoo/new-test')
project = described_class.find(project_legacy_storage_5.id)
expect(project.disk_path).to eq('zoo/new-test')
@@ -93,8 +93,8 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
subgroup.update_column(:parent_id, non_existing_record_id)
project = described_class.find(project_orphaned_namespace.id)
- project.route.destroy
- subgroup.route.destroy
+ project.route.destroy!
+ subgroup.route.destroy!
expect { project.reload.disk_path }
.to raise_error(Gitlab::BackgroundMigration::BackfillProjectRepositories::OrphanedNamespaceError)
diff --git a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
index 4e7a3a33f7e..48c5674822a 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
@@ -5,16 +5,16 @@ require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillProjectSettings, schema: 20200114113341 do
let(:projects) { table(:projects) }
let(:project_settings) { table(:project_settings) }
- let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
- let(:project) { projects.create(namespace_id: namespace.id) }
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+ let(:project) { projects.create!(namespace_id: namespace.id) }
subject { described_class.new }
describe '#perform' do
it 'creates settings for all projects in range' do
- projects.create(id: 5, namespace_id: namespace.id)
- projects.create(id: 7, namespace_id: namespace.id)
- projects.create(id: 8, namespace_id: namespace.id)
+ projects.create!(id: 5, namespace_id: namespace.id)
+ projects.create!(id: 7, namespace_id: namespace.id)
+ projects.create!(id: 8, namespace_id: namespace.id)
subject.perform(5, 7)
diff --git a/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb b/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb
index 39b49d008d4..9ce6a3227b5 100644
--- a/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb
@@ -6,21 +6,21 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillPushRulesIdInProjects, :migr
let(:push_rules) { table(:push_rules) }
let(:projects) { table(:projects) }
let(:project_settings) { table(:project_settings) }
- let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
subject { described_class.new }
describe '#perform' do
it 'creates new project push_rules for all push rules in the range' do
- project_1 = projects.create(id: 1, namespace_id: namespace.id)
- project_2 = projects.create(id: 2, namespace_id: namespace.id)
- project_3 = projects.create(id: 3, namespace_id: namespace.id)
- project_settings_1 = project_settings.create(project_id: project_1.id)
- project_settings_2 = project_settings.create(project_id: project_2.id)
- project_settings_3 = project_settings.create(project_id: project_3.id)
- push_rule_1 = push_rules.create(id: 5, is_sample: false, project_id: project_1.id)
- push_rule_2 = push_rules.create(id: 6, is_sample: false, project_id: project_2.id)
- push_rules.create(id: 8, is_sample: false, project_id: 3)
+ project_1 = projects.create!(id: 1, namespace_id: namespace.id)
+ project_2 = projects.create!(id: 2, namespace_id: namespace.id)
+ project_3 = projects.create!(id: 3, namespace_id: namespace.id)
+ project_settings_1 = project_settings.create!(project_id: project_1.id)
+ project_settings_2 = project_settings.create!(project_id: project_2.id)
+ project_settings_3 = project_settings.create!(project_id: project_3.id)
+ push_rule_1 = push_rules.create!(id: 5, is_sample: false, project_id: project_1.id)
+ push_rule_2 = push_rules.create!(id: 6, is_sample: false, project_id: project_2.id)
+ push_rules.create!(id: 8, is_sample: false, project_id: 3)
subject.perform(5, 7)
diff --git a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
index a23b74bcaca..50e799908c6 100644
--- a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migrat
let(:user_name) { 'Test' }
let!(:user) do
- users.create(id: 1,
+ users.create!(id: 1,
email: 'user@example.com',
projects_limit: 10,
username: 'test',
@@ -25,7 +25,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migrat
end
let!(:migration_bot) do
- users.create(id: 100,
+ users.create!(id: 100,
email: "noreply+gitlab-migration-bot%s@#{Settings.gitlab.host}",
user_type: HasUserType::USER_TYPES[:migration_bot],
name: 'GitLab Migration Bot',
@@ -33,9 +33,9 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migrat
username: 'bot')
end
- let!(:snippet_with_repo) { snippets.create(id: 1, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
- let!(:snippet_with_empty_repo) { snippets.create(id: 2, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
- let!(:snippet_without_repo) { snippets.create(id: 3, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
+ let!(:snippet_with_repo) { snippets.create!(id: 1, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
+ let!(:snippet_with_empty_repo) { snippets.create!(id: 2, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
+ let!(:snippet_without_repo) { snippets.create!(id: 3, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
let(:file_name) { 'file_name.rb' }
let(:content) { 'content' }
@@ -197,8 +197,8 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migrat
end
with_them do
- let!(:snippet_with_invalid_path) { snippets.create(id: 4, type: 'PersonalSnippet', author_id: user.id, file_name: invalid_file_name, content: content) }
- let!(:snippet_with_valid_path) { snippets.create(id: 5, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
+ let!(:snippet_with_invalid_path) { snippets.create!(id: 4, type: 'PersonalSnippet', author_id: user.id, file_name: invalid_file_name, content: content) }
+ let!(:snippet_with_valid_path) { snippets.create!(id: 5, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
let(:ids) { [4, 5] }
after do
@@ -241,7 +241,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migrat
context 'when user name is invalid' do
let(:user_name) { '.' }
- let!(:snippet) { snippets.create(id: 4, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
+ let!(:snippet) { snippets.create!(id: 4, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
let(:ids) { [4, 4] }
after do
@@ -254,7 +254,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migrat
context 'when both user name and snippet file_name are invalid' do
let(:user_name) { '.' }
let!(:other_user) do
- users.create(id: 2,
+ users.create!(id: 2,
email: 'user2@example.com',
projects_limit: 10,
username: 'test2',
@@ -265,8 +265,8 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migrat
confirmed_at: 1.day.ago)
end
- let!(:invalid_snippet) { snippets.create(id: 4, type: 'PersonalSnippet', author_id: user.id, file_name: '.', content: content) }
- let!(:snippet) { snippets.create(id: 5, type: 'PersonalSnippet', author_id: other_user.id, file_name: file_name, content: content) }
+ let!(:invalid_snippet) { snippets.create!(id: 4, type: 'PersonalSnippet', author_id: user.id, file_name: '.', content: content) }
+ let!(:snippet) { snippets.create!(id: 5, type: 'PersonalSnippet', author_id: other_user.id, file_name: file_name, content: content) }
let(:ids) { [4, 5] }
after do
diff --git a/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb b/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
index e2175c41513..d503824041b 100644
--- a/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutProjectFeature, sc
let(:projects) { table(:projects) }
let(:project_features) { table(:project_features) }
- let(:namespace) { namespaces.create(name: 'foo', path: 'foo') }
+ let(:namespace) { namespaces.create!(name: 'foo', path: 'foo') }
let!(:project) { projects.create!(namespace_id: namespace.id) }
let(:private_project_without_feature) { projects.create!(namespace_id: namespace.id, visibility_level: 0) }
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutProjectFeature, sc
let!(:projects_without_feature) { [private_project_without_feature, public_project_without_feature] }
before do
- project_features.create({ project_id: project.id, pages_access_level: 20 })
+ project_features.create!({ project_id: project.id, pages_access_level: 20 })
end
subject { described_class.new.perform(Project.minimum(:id), Project.maximum(:id)) }
diff --git a/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb b/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb
index fe2b206ea74..9a497a9e01a 100644
--- a/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb
@@ -33,8 +33,8 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
let(:clusters) { table(:clusters) }
let(:cluster_groups) { table(:cluster_groups) }
let(:clusters_applications_prometheus) { table(:clusters_applications_prometheus) }
- let(:namespace) { namespaces.create(name: 'user', path: 'user') }
- let(:project) { projects.create(namespace_id: namespace.id) }
+ let(:namespace) { namespaces.create!(name: 'user', path: 'user') }
+ let(:project) { projects.create!(namespace_id: namespace.id) }
let(:application_statuses) do
{
@@ -71,7 +71,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
context 'non prometheus services' do
it 'does not change them' do
other_type = 'SomeOtherService'
- services.create(service_params_for(project.id, active: true, type: other_type))
+ services.create!(service_params_for(project.id, active: true, type: other_type))
expect { subject.perform(project.id, project.id + 1) }.not_to change { services.where(type: other_type).order(:id).map { |row| row.attributes } }
end
@@ -85,7 +85,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
context 'template is present for prometheus services' do
it 'creates missing services entries', :aggregate_failures do
- services.create(service_params_for(nil, template: true, properties: { 'from_template' => true }.to_json))
+ services.create!(service_params_for(nil, template: true, properties: { 'from_template' => true }.to_json))
expect { subject.perform(project.id, project.id + 1) }.to change { services.count }.by(1)
updated_rows = services.where(template: false).order(:id).map { |row| row.attributes.slice(*columns).symbolize_keys }
@@ -97,7 +97,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
context 'prometheus integration services exist' do
context 'in active state' do
it 'does not change them' do
- services.create(service_params_for(project.id, active: true))
+ services.create!(service_params_for(project.id, active: true))
expect { subject.perform(project.id, project.id + 1) }.not_to change { services.order(:id).map { |row| row.attributes } }
end
@@ -105,7 +105,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
context 'not in active state' do
it 'sets active attribute to true' do
- service = services.create(service_params_for(project.id, active: false))
+ service = services.create!(service_params_for(project.id, active: false))
expect { subject.perform(project.id, project.id + 1) }.to change { service.reload.active? }.from(false).to(true)
end
@@ -113,7 +113,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
context 'prometheus services are configured manually ' do
it 'does not change them' do
properties = '{"api_url":"http://test.dev","manual_configuration":"1"}'
- services.create(service_params_for(project.id, properties: properties, active: false))
+ services.create!(service_params_for(project.id, properties: properties, active: false))
expect { subject.perform(project.id, project.id + 1) }.not_to change { services.order(:id).map { |row| row.attributes } }
end
@@ -123,11 +123,11 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
end
context 'k8s cluster shared on instance level' do
- let(:cluster) { clusters.create(name: 'cluster', cluster_type: cluster_types[:instance_type]) }
+ let(:cluster) { clusters.create!(name: 'cluster', cluster_type: cluster_types[:instance_type]) }
context 'with installed prometheus application' do
before do
- clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:installed], version: '123')
+ clusters_applications_prometheus.create!(cluster_id: cluster.id, status: application_statuses[:installed], version: '123')
end
it_behaves_like 'fix services entries state'
@@ -135,7 +135,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
context 'with updated prometheus application' do
before do
- clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:updated], version: '123')
+ clusters_applications_prometheus.create!(cluster_id: cluster.id, status: application_statuses[:updated], version: '123')
end
it_behaves_like 'fix services entries state'
@@ -143,7 +143,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
context 'with errored prometheus application' do
before do
- clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:errored], version: '123')
+ clusters_applications_prometheus.create!(cluster_id: cluster.id, status: application_statuses[:errored], version: '123')
end
it 'does not change services entries' do
@@ -153,26 +153,26 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
end
context 'k8s cluster shared on group level' do
- let(:cluster) { clusters.create(name: 'cluster', cluster_type: cluster_types[:group_type]) }
+ let(:cluster) { clusters.create!(name: 'cluster', cluster_type: cluster_types[:group_type]) }
before do
- cluster_groups.create(cluster_id: cluster.id, group_id: project.namespace_id)
+ cluster_groups.create!(cluster_id: cluster.id, group_id: project.namespace_id)
end
context 'with installed prometheus application' do
before do
- clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:installed], version: '123')
+ clusters_applications_prometheus.create!(cluster_id: cluster.id, status: application_statuses[:installed], version: '123')
end
it_behaves_like 'fix services entries state'
context 'second k8s cluster without application available' do
- let(:namespace_2) { namespaces.create(name: 'namespace2', path: 'namespace2') }
- let(:project_2) { projects.create(namespace_id: namespace_2.id) }
+ let(:namespace_2) { namespaces.create!(name: 'namespace2', path: 'namespace2') }
+ let(:project_2) { projects.create!(namespace_id: namespace_2.id) }
before do
- cluster_2 = clusters.create(name: 'cluster2', cluster_type: cluster_types[:group_type])
- cluster_groups.create(cluster_id: cluster_2.id, group_id: project_2.namespace_id)
+ cluster_2 = clusters.create!(name: 'cluster2', cluster_type: cluster_types[:group_type])
+ cluster_groups.create!(cluster_id: cluster_2.id, group_id: project_2.namespace_id)
end
it 'changed only affected services entries' do
@@ -184,7 +184,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
context 'with updated prometheus application' do
before do
- clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:updated], version: '123')
+ clusters_applications_prometheus.create!(cluster_id: cluster.id, status: application_statuses[:updated], version: '123')
end
it_behaves_like 'fix services entries state'
@@ -192,7 +192,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
context 'with errored prometheus application' do
before do
- clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:errored], version: '123')
+ clusters_applications_prometheus.create!(cluster_id: cluster.id, status: application_statuses[:errored], version: '123')
end
it 'does not change services entries' do
@@ -207,7 +207,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
context 'with inactive service' do
it 'does not change services entries' do
- services.create(service_params_for(project.id))
+ services.create!(service_params_for(project.id))
expect { subject.perform(project.id, project.id + 1) }.not_to change { services.order(:id).map { |row| row.attributes } }
end
@@ -216,13 +216,13 @@ RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService,
end
context 'k8s cluster for single project' do
- let(:cluster) { clusters.create(name: 'cluster', cluster_type: cluster_types[:project_type]) }
+ let(:cluster) { clusters.create!(name: 'cluster', cluster_type: cluster_types[:project_type]) }
let(:cluster_projects) { table(:cluster_projects) }
context 'with installed prometheus application' do
before do
- cluster_projects.create(cluster_id: cluster.id, project_id: project.id)
- clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:installed], version: '123')
+ cluster_projects.create!(cluster_id: cluster.id, project_id: project.id)
+ clusters_applications_prometheus.create!(cluster_id: cluster.id, status: application_statuses[:installed], version: '123')
end
it 'does not change services entries' do
diff --git a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
index 7768411828c..0d0ad2cc39e 100644
--- a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
@@ -5,18 +5,18 @@ require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190620112608 do
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
- let(:user) { users.create(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
+ let(:user) { users.create!(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
context 'updating the namespace names' do
it 'updates a user namespace within range' do
- user2 = users.create(name: "Other user's full name", projects_limit: 10, username: 'also-not-null', email: '2')
- user_namespace1 = namespaces.create(
+ user2 = users.create!(name: "Other user's full name", projects_limit: 10, username: 'also-not-null', email: '2')
+ user_namespace1 = namespaces.create!(
id: 2,
owner_id: user.id,
name: "Should be the user's name",
path: user.username
)
- user_namespace2 = namespaces.create(
+ user_namespace2 = namespaces.create!(
id: 3,
owner_id: user2.id,
name: "Should also be the user's name",
@@ -30,7 +30,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190
end
it 'does not update namespaces out of range' do
- user_namespace = namespaces.create(
+ user_namespace = namespaces.create!(
id: 6,
owner_id: user.id,
name: "Should be the user's name",
@@ -42,7 +42,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190
end
it 'does not update groups owned by the users' do
- user_group = namespaces.create(
+ user_group = namespaces.create!(
id: 2,
owner_id: user.id,
name: 'A group name',
@@ -58,7 +58,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190
context 'namespace route names' do
let(:routes) { table(:routes) }
let(:namespace) do
- namespaces.create(
+ namespaces.create!(
id: 2,
owner_id: user.id,
name: "Will be updated to the user's name",
@@ -67,7 +67,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190
end
it "updates the route name if it didn't match the namespace" do
- route = routes.create(path: namespace.path, name: 'Incorrect name', source_type: 'Namespace', source_id: namespace.id)
+ route = routes.create!(path: namespace.path, name: 'Incorrect name', source_type: 'Namespace', source_id: namespace.id)
described_class.new.perform(1, 5)
@@ -75,7 +75,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190
end
it 'updates the route name if it was nil match the namespace' do
- route = routes.create(path: namespace.path, name: nil, source_type: 'Namespace', source_id: namespace.id)
+ route = routes.create!(path: namespace.path, name: nil, source_type: 'Namespace', source_id: namespace.id)
described_class.new.perform(1, 5)
@@ -83,14 +83,14 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190
end
it "doesn't update group routes" do
- route = routes.create(path: 'group-path', name: 'Group name', source_type: 'Group', source_id: namespace.id)
+ route = routes.create!(path: 'group-path', name: 'Group name', source_type: 'Group', source_id: namespace.id)
expect { described_class.new.perform(1, 5) }
.not_to change { route.reload.name }
end
it "doesn't touch routes for namespaces out of range" do
- user_namespace = namespaces.create(
+ user_namespace = namespaces.create!(
id: 6,
owner_id: user.id,
name: "Should be the user's name",
diff --git a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
index 4c04043ebd0..211693d917b 100644
--- a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
@@ -8,10 +8,10 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20
let(:routes) { table(:routes) }
let(:projects) { table(:projects) }
- let(:user) { users.create(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
+ let(:user) { users.create!(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
let(:namespace) do
- namespaces.create(
+ namespaces.create!(
owner_id: user.id,
name: "Should eventually be the user's name",
path: user.username
@@ -19,11 +19,11 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20
end
let(:project) do
- projects.create(namespace_id: namespace.id, name: 'Project Name')
+ projects.create!(namespace_id: namespace.id, name: 'Project Name')
end
it "updates the route for a project if it did not match the user's name" do
- route = routes.create(
+ route = routes.create!(
id: 1,
path: "#{user.username}/#{project.path}",
source_id: project.id,
@@ -37,7 +37,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20
end
it 'updates the route for a project if the name was nil' do
- route = routes.create(
+ route = routes.create!(
id: 1,
path: "#{user.username}/#{project.path}",
source_id: project.id,
@@ -51,7 +51,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20
end
it 'does not update routes that were are out of the range' do
- route = routes.create(
+ route = routes.create!(
id: 6,
path: "#{user.username}/#{project.path}",
source_id: project.id,
@@ -64,14 +64,14 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20
end
it 'does not update routes for projects in groups owned by the user' do
- group = namespaces.create(
+ group = namespaces.create!(
owner_id: user.id,
name: 'A group',
path: 'a-path',
type: ''
)
- project = projects.create(namespace_id: group.id, name: 'Project Name')
- route = routes.create(
+ project = projects.create!(namespace_id: group.id, name: 'Project Name')
+ route = routes.create!(
id: 1,
path: "#{group.path}/#{project.path}",
source_id: project.id,
@@ -84,7 +84,7 @@ RSpec.describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20
end
it 'does not update routes for namespaces' do
- route = routes.create(
+ route = routes.create!(
id: 1,
path: namespace.path,
source_id: namespace.id,
diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
index 934ab7e37f8..264faa4de3b 100644
--- a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
+++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failur
if with_file
upload = create(:upload, :with_file, :attachment_upload, params)
- model.update(attachment: upload.retrieve_uploader)
+ model.update!(attachment: upload.retrieve_uploader)
model.attachment.upload
else
create(:upload, :attachment_upload, params)
@@ -245,7 +245,7 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failur
end
let(:connection) { ::Fog::Storage.new(FileUploader.object_store_credentials) }
- let(:bucket) { connection.directories.create(key: 'uploads') }
+ let(:bucket) { connection.directories.create(key: 'uploads') } # rubocop:disable Rails/SaveBang
before do
stub_uploads_object_storage(FileUploader)
@@ -257,7 +257,7 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failur
context 'when the file belongs to a legacy project' do
before do
- bucket.files.create(remote_file)
+ bucket.files.create(remote_file) # rubocop:disable Rails/SaveBang
end
let(:project) { legacy_project }
@@ -267,7 +267,7 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failur
context 'when the file belongs to a hashed project' do
before do
- bucket.files.create(remote_file)
+ bucket.files.create(remote_file) # rubocop:disable Rails/SaveBang
end
let(:project) { hashed_project }
diff --git a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
index 66a1787b2cb..7227f80a062 100644
--- a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
+++ b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do
if with_file
upload = create(:upload, :with_file, :attachment_upload, params)
- model.update(attachment: upload.retrieve_uploader)
+ model.update!(attachment: upload.retrieve_uploader)
model.attachment.upload
else
create(:upload, :attachment_upload, params)
diff --git a/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb b/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
index dda4f5a3a36..b7cf101dd8a 100644
--- a/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
+++ b/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
@@ -10,44 +10,44 @@ RSpec.describe Gitlab::BackgroundMigration::LinkLfsObjectsProjects, :migration,
let(:lfs_objects) { table(:lfs_objects) }
let(:lfs_objects_projects) { table(:lfs_objects_projects) }
- let(:namespace) { namespaces.create(name: 'GitLab', path: 'gitlab') }
+ let(:namespace) { namespaces.create!(name: 'GitLab', path: 'gitlab') }
- let(:fork_network) { fork_networks.create(root_project_id: source_project.id) }
- let(:another_fork_network) { fork_networks.create(root_project_id: another_source_project.id) }
+ let(:fork_network) { fork_networks.create!(root_project_id: source_project.id) }
+ let(:another_fork_network) { fork_networks.create!(root_project_id: another_source_project.id) }
- let(:source_project) { projects.create(namespace_id: namespace.id) }
- let(:another_source_project) { projects.create(namespace_id: namespace.id) }
- let(:project) { projects.create(namespace_id: namespace.id) }
- let(:another_project) { projects.create(namespace_id: namespace.id) }
- let(:partially_linked_project) { projects.create(namespace_id: namespace.id) }
- let(:fully_linked_project) { projects.create(namespace_id: namespace.id) }
+ let(:source_project) { projects.create!(namespace_id: namespace.id) }
+ let(:another_source_project) { projects.create!(namespace_id: namespace.id) }
+ let(:project) { projects.create!(namespace_id: namespace.id) }
+ let(:another_project) { projects.create!(namespace_id: namespace.id) }
+ let(:partially_linked_project) { projects.create!(namespace_id: namespace.id) }
+ let(:fully_linked_project) { projects.create!(namespace_id: namespace.id) }
- let(:lfs_object) { lfs_objects.create(oid: 'abc123', size: 100) }
- let(:another_lfs_object) { lfs_objects.create(oid: 'def456', size: 200) }
+ let(:lfs_object) { lfs_objects.create!(oid: 'abc123', size: 100) }
+ let(:another_lfs_object) { lfs_objects.create!(oid: 'def456', size: 200) }
let!(:source_project_lop_1) do
- lfs_objects_projects.create(
+ lfs_objects_projects.create!(
lfs_object_id: lfs_object.id,
project_id: source_project.id
)
end
let!(:source_project_lop_2) do
- lfs_objects_projects.create(
+ lfs_objects_projects.create!(
lfs_object_id: another_lfs_object.id,
project_id: source_project.id
)
end
let!(:another_source_project_lop_1) do
- lfs_objects_projects.create(
+ lfs_objects_projects.create!(
lfs_object_id: lfs_object.id,
project_id: another_source_project.id
)
end
let!(:another_source_project_lop_2) do
- lfs_objects_projects.create(
+ lfs_objects_projects.create!(
lfs_object_id: another_lfs_object.id,
project_id: another_source_project.id
)
@@ -57,23 +57,23 @@ RSpec.describe Gitlab::BackgroundMigration::LinkLfsObjectsProjects, :migration,
stub_const("#{described_class}::BATCH_SIZE", 2)
# Create links between projects
- fork_network_members.create(fork_network_id: fork_network.id, project_id: source_project.id, forked_from_project_id: nil)
+ fork_network_members.create!(fork_network_id: fork_network.id, project_id: source_project.id, forked_from_project_id: nil)
[project, partially_linked_project, fully_linked_project].each do |p|
- fork_network_members.create(
+ fork_network_members.create!(
fork_network_id: fork_network.id,
project_id: p.id,
forked_from_project_id: fork_network.root_project_id
)
end
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: another_source_project.id, forked_from_project_id: nil)
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: another_project.id, forked_from_project_id: another_fork_network.root_project_id)
+ fork_network_members.create!(fork_network_id: another_fork_network.id, project_id: another_source_project.id, forked_from_project_id: nil)
+ fork_network_members.create!(fork_network_id: another_fork_network.id, project_id: another_project.id, forked_from_project_id: another_fork_network.root_project_id)
# Links LFS objects to some projects
- lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: fully_linked_project.id)
- lfs_objects_projects.create(lfs_object_id: another_lfs_object.id, project_id: fully_linked_project.id)
- lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: partially_linked_project.id)
+ lfs_objects_projects.create!(lfs_object_id: lfs_object.id, project_id: fully_linked_project.id)
+ lfs_objects_projects.create!(lfs_object_id: another_lfs_object.id, project_id: fully_linked_project.id)
+ lfs_objects_projects.create!(lfs_object_id: lfs_object.id, project_id: partially_linked_project.id)
end
context 'when there are LFS objects to be linked' do
@@ -96,8 +96,8 @@ RSpec.describe Gitlab::BackgroundMigration::LinkLfsObjectsProjects, :migration,
before do
# Links LFS objects to all projects
projects.all.each do |p|
- lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: p.id)
- lfs_objects_projects.create(lfs_object_id: another_lfs_object.id, project_id: p.id)
+ lfs_objects_projects.create!(lfs_object_id: lfs_object.id, project_id: p.id)
+ lfs_objects_projects.create!(lfs_object_id: another_lfs_object.id, project_id: p.id)
end
end
diff --git a/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb b/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
index d829fd5daf5..8668216d014 100644
--- a/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
@@ -97,7 +97,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'with Jira service' do
let!(:service) do
- services.create(id: 10, type: 'JiraService', title: nil, properties: jira_properties.to_json, category: 'issue_tracker')
+ services.create!(id: 10, type: 'JiraService', title: nil, properties: jira_properties.to_json, category: 'issue_tracker')
end
it_behaves_like 'handle properties'
@@ -119,7 +119,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'with bugzilla service' do
let!(:service) do
- services.create(id: 11, type: 'BugzillaService', title: nil, properties: tracker_properties.to_json, category: 'issue_tracker')
+ services.create!(id: 11, type: 'BugzillaService', title: nil, properties: tracker_properties.to_json, category: 'issue_tracker')
end
it_behaves_like 'handle properties'
@@ -140,7 +140,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'with youtrack service' do
let!(:service) do
- services.create(id: 12, type: 'YoutrackService', title: nil, properties: tracker_properties_no_url.to_json, category: 'issue_tracker')
+ services.create!(id: 12, type: 'YoutrackService', title: nil, properties: tracker_properties_no_url.to_json, category: 'issue_tracker')
end
it_behaves_like 'handle properties'
@@ -161,7 +161,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'with gitlab service with no properties' do
let!(:service) do
- services.create(id: 13, type: 'GitlabIssueTrackerService', title: nil, properties: {}, category: 'issue_tracker')
+ services.create!(id: 13, type: 'GitlabIssueTrackerService', title: nil, properties: {}, category: 'issue_tracker')
end
it_behaves_like 'handle properties'
@@ -173,7 +173,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'with redmine service already with data fields' do
let!(:service) do
- services.create(id: 14, type: 'RedmineService', title: nil, properties: tracker_properties_no_url.to_json, category: 'issue_tracker').tap do |service|
+ services.create!(id: 14, type: 'RedmineService', title: nil, properties: tracker_properties_no_url.to_json, category: 'issue_tracker').tap do |service|
IssueTrackerData.create!(service_id: service.id, project_url: url, new_issue_url: new_issue_url, issues_url: issues_url)
end
end
@@ -187,7 +187,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'with custom issue tracker which has data fields record inconsistent with properties field' do
let!(:service) do
- services.create(id: 15, type: 'CustomIssueTrackerService', title: 'Existing title', properties: jira_properties.to_json, category: 'issue_tracker').tap do |service|
+ services.create!(id: 15, type: 'CustomIssueTrackerService', title: 'Existing title', properties: jira_properties.to_json, category: 'issue_tracker').tap do |service|
IssueTrackerData.create!(service_id: service.id, project_url: 'http://other_url', new_issue_url: 'http://other_url/new_issue', issues_url: 'http://other_url/issues')
end
end
@@ -209,7 +209,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'with Jira service which has data fields record inconsistent with properties field' do
let!(:service) do
- services.create(id: 16, type: 'CustomIssueTrackerService', description: 'Existing description', properties: jira_properties.to_json, category: 'issue_tracker').tap do |service|
+ services.create!(id: 16, type: 'CustomIssueTrackerService', description: 'Existing description', properties: jira_properties.to_json, category: 'issue_tracker').tap do |service|
JiraTrackerData.create!(service_id: service.id, url: 'http://other_jira_url')
end
end
@@ -232,7 +232,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'non issue tracker service' do
let!(:service) do
- services.create(id: 17, title: nil, description: nil, type: 'OtherService', properties: tracker_properties.to_json)
+ services.create!(id: 17, title: nil, description: nil, type: 'OtherService', properties: tracker_properties.to_json)
end
it_behaves_like 'handle properties'
@@ -248,7 +248,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'Jira service with empty properties' do
let!(:service) do
- services.create(id: 18, type: 'JiraService', properties: '', category: 'issue_tracker')
+ services.create!(id: 18, type: 'JiraService', properties: '', category: 'issue_tracker')
end
it_behaves_like 'handle properties'
@@ -260,7 +260,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'Jira service with nil properties' do
let!(:service) do
- services.create(id: 18, type: 'JiraService', properties: nil, category: 'issue_tracker')
+ services.create!(id: 18, type: 'JiraService', properties: nil, category: 'issue_tracker')
end
it_behaves_like 'handle properties'
@@ -272,7 +272,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'Jira service with invalid properties' do
let!(:service) do
- services.create(id: 18, type: 'JiraService', properties: 'invalid data', category: 'issue_tracker')
+ services.create!(id: 18, type: 'JiraService', properties: 'invalid data', category: 'issue_tracker')
end
it_behaves_like 'handle properties'
@@ -284,15 +284,15 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, s
context 'with Jira service with invalid properties, valid Jira service and valid bugzilla service' do
let!(:jira_service_invalid) do
- services.create(id: 19, title: 'invalid - title', description: 'invalid - description', type: 'JiraService', properties: 'invalid data', category: 'issue_tracker')
+ services.create!(id: 19, title: 'invalid - title', description: 'invalid - description', type: 'JiraService', properties: 'invalid data', category: 'issue_tracker')
end
let!(:jira_service_valid) do
- services.create(id: 20, type: 'JiraService', properties: jira_properties.to_json, category: 'issue_tracker')
+ services.create!(id: 20, type: 'JiraService', properties: jira_properties.to_json, category: 'issue_tracker')
end
let!(:bugzilla_service_valid) do
- services.create(id: 11, type: 'BugzillaService', title: nil, properties: tracker_properties.to_json, category: 'issue_tracker')
+ services.create!(id: 11, type: 'BugzillaService', title: nil, properties: tracker_properties.to_json, category: 'issue_tracker')
end
it 'migrates data for the valid service' do
diff --git a/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb b/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb
index 3cec5cb4c35..d90a5d30954 100644
--- a/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb
@@ -11,17 +11,17 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateUsersBioToUserDetails, :migra
klass
end
- let!(:user_needs_migration) { users.create(name: 'user1', email: 'test1@test.com', projects_limit: 1, bio: 'bio') }
- let!(:user_needs_no_migration) { users.create(name: 'user2', email: 'test2@test.com', projects_limit: 1) }
- let!(:user_also_needs_no_migration) { users.create(name: 'user3', email: 'test3@test.com', projects_limit: 1, bio: '') }
- let!(:user_with_long_bio) { users.create(name: 'user4', email: 'test4@test.com', projects_limit: 1, bio: 'a' * 256) } # 255 is the max
+ let!(:user_needs_migration) { users.create!(name: 'user1', email: 'test1@test.com', projects_limit: 1, bio: 'bio') }
+ let!(:user_needs_no_migration) { users.create!(name: 'user2', email: 'test2@test.com', projects_limit: 1) }
+ let!(:user_also_needs_no_migration) { users.create!(name: 'user3', email: 'test3@test.com', projects_limit: 1, bio: '') }
+ let!(:user_with_long_bio) { users.create!(name: 'user4', email: 'test4@test.com', projects_limit: 1, bio: 'a' * 256) } # 255 is the max
- let!(:user_already_has_details) { users.create(name: 'user5', email: 'test5@test.com', projects_limit: 1, bio: 'my bio') }
- let!(:existing_user_details) { user_details.find_or_create_by(user_id: user_already_has_details.id).update(bio: 'my bio') }
+ let!(:user_already_has_details) { users.create!(name: 'user5', email: 'test5@test.com', projects_limit: 1, bio: 'my bio') }
+ let!(:existing_user_details) { user_details.find_or_create_by!(user_id: user_already_has_details.id).update!(bio: 'my bio') }
# unlikely scenario since we have triggers
- let!(:user_has_different_details) { users.create(name: 'user6', email: 'test6@test.com', projects_limit: 1, bio: 'different') }
- let!(:different_existing_user_details) { user_details.find_or_create_by(user_id: user_has_different_details.id).update(bio: 'bio') }
+ let!(:user_has_different_details) { users.create!(name: 'user6', email: 'test6@test.com', projects_limit: 1, bio: 'different') }
+ let!(:different_existing_user_details) { user_details.find_or_create_by!(user_id: user_has_different_details.id).update!(bio: 'bio') }
let(:user_ids) do
[
diff --git a/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb b/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb
index ee0024e8526..36000dc3ffd 100644
--- a/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb
@@ -63,7 +63,7 @@ RSpec.describe Gitlab::BackgroundMigration::PopulateCanonicalEmails, :migration,
describe 'gracefully handles existing records, some of which may have an already-existing identical canonical_email field' do
let_it_be(:user_one) { create_user(email: "example.user@gmail.com", id: 1) }
let_it_be(:user_two) { create_user(email: "exampleuser@gmail.com", id: 2) }
- let_it_be(:user_email_one) { user_canonical_emails.create(canonical_email: "exampleuser@gmail.com", user_id: user_one.id) }
+ let_it_be(:user_email_one) { user_canonical_emails.create!(canonical_email: "exampleuser@gmail.com", user_id: user_one.id) }
subject { migration.perform(1, 2) }
@@ -79,7 +79,7 @@ RSpec.describe Gitlab::BackgroundMigration::PopulateCanonicalEmails, :migration,
projects_limit: 0
}
- users.create(default_attributes.merge!(attributes))
+ users.create!(default_attributes.merge!(attributes))
end
def canonical_emails(user_id: nil)
diff --git a/spec/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities_spec.rb b/spec/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities_spec.rb
new file mode 100644
index 00000000000..bc55f240a58
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Gitlab::BackgroundMigration::PopulateDismissedStateForVulnerabilities, schema: 2020_11_30_103926 do
+ let(:users) { table(:users) }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:vulnerabilities) { table(:vulnerabilities) }
+
+ let!(:namespace) { namespaces.create!(name: "foo", path: "bar") }
+ let!(:user) { users.create!(name: 'John Doe', email: 'test@example.com', projects_limit: 5) }
+ let!(:project) { projects.create!(namespace_id: namespace.id) }
+ let!(:vulnerability_params) do
+ {
+ project_id: project.id,
+ author_id: user.id,
+ title: 'Vulnerability',
+ severity: 5,
+ confidence: 5,
+ report_type: 5
+ }
+ end
+
+ let!(:vulnerability_1) { vulnerabilities.create!(vulnerability_params.merge(state: 1)) }
+ let!(:vulnerability_2) { vulnerabilities.create!(vulnerability_params.merge(state: 3)) }
+
+ describe '#perform' do
+ it 'changes state of vulnerability to dismissed' do
+ subject.perform(vulnerability_1.id, vulnerability_2.id)
+
+ expect(vulnerability_1.reload.state).to eq(2)
+ expect(vulnerability_2.reload.state).to eq(2)
+ end
+
+ it 'populates missing dismissal information' do
+ expect_next_instance_of(::Gitlab::BackgroundMigration::PopulateMissingVulnerabilityDismissalInformation) do |migration|
+ expect(migration).to receive(:perform).with(vulnerability_1.id, vulnerability_2.id)
+ end
+
+ subject.perform(vulnerability_1.id, vulnerability_2.id)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
index 1e5773ee16b..4e7872a9a1b 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
@@ -11,8 +11,8 @@ RSpec.describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable,
let(:user_2) { users.create!(email: 'test2@example.com', projects_limit: 100, username: 'test') }
let(:user_3) { users.create!(email: 'test3@example.com', projects_limit: 100, username: 'test') }
- let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
- let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') }
let(:merge_requests) { table(:merge_requests) }
let(:merge_request_assignees) { table(:merge_request_assignees) }
@@ -24,7 +24,7 @@ RSpec.describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable,
source_branch: 'mr name',
title: "mr name#{id}")
- merge_requests.create(params)
+ merge_requests.create!(params)
end
before do
diff --git a/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb b/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb
index f0b0f77280e..b3cacc60cdc 100644
--- a/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Gitlab::BackgroundMigration::PopulateUserHighestRolesTable, schem
projects_limit: 0
}.merge(params)
- users.create(user_params)
+ users.create!(user_params)
end
def create_member(id, access_level, params = {})
@@ -30,7 +30,7 @@ RSpec.describe Gitlab::BackgroundMigration::PopulateUserHighestRolesTable, schem
notification_level: 0
}.merge(params)
- members.create(params)
+ members.create!(params)
end
before do
@@ -47,7 +47,7 @@ RSpec.describe Gitlab::BackgroundMigration::PopulateUserHighestRolesTable, schem
create_member(7, 30)
create_member(8, 20, requested_at: Time.current)
- user_highest_roles.create(user_id: 1, highest_access_level: 50)
+ user_highest_roles.create!(user_id: 1, highest_access_level: 50)
end
describe '#perform' do
diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
index 33e1f31d1f1..1c55b50ea3f 100644
--- a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
+++ b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
@@ -91,7 +91,7 @@ RSpec.describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, sc
projects_table.create!(id: 1, name: 'project', path: 'project', visibility_level: 0,
namespace_id: shared_group.id)
- group_group_links.create(shared_group_id: shared_group.id, shared_with_group_id: group.id,
+ group_group_links.create!(shared_group_id: shared_group.id, shared_with_group_id: group.id,
group_access: 20)
end
@@ -111,7 +111,7 @@ RSpec.describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, sc
shared_project = projects_table.create!(id: 1, name: 'shared project', path: 'shared-project',
visibility_level: 0, namespace_id: another_group.id)
- project_group_links.create(project_id: shared_project.id, group_id: group.id, group_access: 20)
+ project_group_links.create!(project_id: shared_project.id, group_id: group.id, group_access: 20)
end
it 'creates correct authorization' do
@@ -174,7 +174,7 @@ RSpec.describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, sc
projects_table.create!(id: 1, name: 'project', path: 'project', visibility_level: 0,
namespace_id: shared_group.id)
- group_group_links.create(shared_group_id: shared_group.id, shared_with_group_id: group.id,
+ group_group_links.create!(shared_group_id: shared_group.id, shared_with_group_id: group.id,
group_access: 20)
end
@@ -192,7 +192,7 @@ RSpec.describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, sc
shared_project = projects_table.create!(id: 1, name: 'shared project', path: 'shared-project',
visibility_level: 0, namespace_id: another_group.id)
- project_group_links.create(project_id: shared_project.id, group_id: group.id, group_access: 20)
+ project_group_links.create!(project_id: shared_project.id, group_id: group.id, group_access: 20)
end
it 'does not create authorization' do
diff --git a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
index 43fc0fb3691..2f5074649c4 100644
--- a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
+++ b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
@@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::ResetMergeStatus do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
- let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
- let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') }
let(:merge_requests) { table(:merge_requests) }
def create_merge_request(id, extra_params = {})
diff --git a/spec/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth_spec.rb b/spec/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth_spec.rb
new file mode 100644
index 00000000000..bebb398413b
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::UpdateExistingUsersThatRequireTwoFactorAuth, schema: 20201030121314 do
+ include MigrationHelpers::NamespacesHelpers
+
+ let(:group_with_2fa_parent) { create_namespace('parent', Gitlab::VisibilityLevel::PRIVATE) }
+ let(:group_with_2fa_child) { create_namespace('child', Gitlab::VisibilityLevel::PRIVATE, parent_id: group_with_2fa_parent.id) }
+ let(:members_table) { table(:members) }
+ let(:users_table) { table(:users) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ context 'with group members' do
+ let(:user_1) { create_user('user@example.com') }
+ let!(:member) { create_group_member(user_1, group_with_2fa_parent) }
+ let!(:user_without_group) { create_user('user_without@example.com') }
+ let(:user_other) { create_user('user_other@example.com') }
+ let!(:member_other) { create_group_member(user_other, group_with_2fa_parent) }
+
+ it 'updates user when user should not be required to establish two factor authentication' do
+ subject.perform(user_1.id, user_without_group.id)
+
+ expect(user_1.reload.require_two_factor_authentication_from_group).to eq(false)
+ end
+
+ it 'does not update user when user is member of group that requires two factor authentication' do
+ group = create_namespace('other', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: true)
+ create_group_member(user_1, group)
+
+ subject.perform(user_1.id, user_without_group.id)
+
+ expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'does not update user who is not in current batch' do
+ subject.perform(user_1.id, user_without_group.id)
+
+ expect(user_other.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'updates all users in current batch' do
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_other.reload.require_two_factor_authentication_from_group).to eq(false)
+ end
+
+ it 'does not update user when user is member of group which parent group requires two factor authentication' do
+ group_with_2fa_parent.update!(require_two_factor_authentication: true)
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'does not update user when user is member of group which has subgroup that requires two factor authentication' do
+ create_namespace('subgroup', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: true, parent_id: group_with_2fa_child.id)
+
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+ end
+ end
+
+ def create_user(email, require_2fa: true)
+ users_table.create!(email: email, projects_limit: 10, require_two_factor_authentication_from_group: require_2fa)
+ end
+
+ def create_group_member(user, group)
+ members_table.create!(user_id: user.id, source_id: group.id, access_level: GroupMember::MAINTAINER, source_type: "Namespace", type: "GroupMember", notification_level: 3)
+ end
+end
diff --git a/spec/lib/gitlab/checks/diff_check_spec.rb b/spec/lib/gitlab/checks/diff_check_spec.rb
index 2cca0aed9c6..f4daafb1d0e 100644
--- a/spec/lib/gitlab/checks/diff_check_spec.rb
+++ b/spec/lib/gitlab/checks/diff_check_spec.rb
@@ -7,7 +7,6 @@ RSpec.describe Gitlab::Checks::DiffCheck do
describe '#validate!' do
let(:owner) { create(:user) }
- let!(:lock) { create(:lfs_file_lock, user: owner, project: project, path: 'README') }
before do
allow(project.repository).to receive(:new_commits).and_return(
@@ -28,13 +27,27 @@ RSpec.describe Gitlab::Checks::DiffCheck do
end
context 'with LFS enabled' do
+ let!(:lock) { create(:lfs_file_lock, user: owner, project: project, path: 'README') }
+
before do
allow(project).to receive(:lfs_enabled?).and_return(true)
end
context 'when change is sent by a different user' do
- it 'raises an error if the user is not allowed to update the file' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "The path 'README' is locked in Git LFS by #{lock.user.name}")
+ context 'when diff check with paths rpc feature flag is true' do
+ it 'raises an error if the user is not allowed to update the file' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "The path 'README' is locked in Git LFS by #{lock.user.name}")
+ end
+ end
+
+ context 'when diff check with paths rpc feature flag is false' do
+ before do
+ stub_feature_flags(diff_check_with_paths_changed_rpc: false)
+ end
+
+ it 'raises an error if the user is not allowed to update the file' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "The path 'README' is locked in Git LFS by #{lock.user.name}")
+ end
end
end
@@ -53,6 +66,8 @@ RSpec.describe Gitlab::Checks::DiffCheck do
expect_any_instance_of(Commit).to receive(:raw_deltas).and_call_original
+ stub_feature_flags(diff_check_with_paths_changed_rpc: false)
+
subject.validate!
end
diff --git a/spec/lib/gitlab/checks/push_check_spec.rb b/spec/lib/gitlab/checks/push_check_spec.rb
index 45ab13cf0cf..262438256b4 100644
--- a/spec/lib/gitlab/checks/push_check_spec.rb
+++ b/spec/lib/gitlab/checks/push_check_spec.rb
@@ -18,5 +18,26 @@ RSpec.describe Gitlab::Checks::PushCheck do
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You are not allowed to push code to this project.')
end
end
+
+ context 'when using a DeployKeyAccess instance' do
+ let(:deploy_key) { create(:deploy_key) }
+ let(:user_access) { Gitlab::DeployKeyAccess.new(deploy_key, container: project) }
+
+ context 'when the deploy key cannot push to the targetted branch' do
+ it 'raises an error' do
+ allow(user_access).to receive(:can_push_to_branch?).and_return(false)
+
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You are not allowed to push code to this project.')
+ end
+ end
+
+ context 'when the deploy key can push to the targetted branch' do
+ it 'is valid' do
+ allow(user_access).to receive(:can_push_to_branch?).and_return(true)
+
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/checks/snippet_check_spec.rb b/spec/lib/gitlab/checks/snippet_check_spec.rb
index 037de8e9369..89417aaca4d 100644
--- a/spec/lib/gitlab/checks/snippet_check_spec.rb
+++ b/spec/lib/gitlab/checks/snippet_check_spec.rb
@@ -9,19 +9,30 @@ RSpec.describe Gitlab::Checks::SnippetCheck do
let(:user_access) { Gitlab::UserAccessSnippet.new(user, snippet: snippet) }
let(:default_branch) { snippet.default_branch }
+ let(:branch_name) { default_branch }
+ let(:creation) { false }
+ let(:deletion) { false }
- subject { Gitlab::Checks::SnippetCheck.new(changes, default_branch: default_branch, logger: logger) }
+ subject { Gitlab::Checks::SnippetCheck.new(changes, default_branch: default_branch, root_ref: snippet.repository.root_ref, logger: logger) }
describe '#validate!' do
it 'does not raise any error' do
expect { subject.validate! }.not_to raise_error
end
+ shared_examples 'raises and logs error' do
+ specify do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(instance_of(Gitlab::GitAccess::ForbiddenError), default_branch: default_branch, branch_name: branch_name, creation: creation, deletion: deletion)
+
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can not create or delete branches.')
+ end
+ end
+
context 'trying to delete the branch' do
let(:newrev) { '0000000000000000000000000000000000000000' }
- it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can not create or delete branches.')
+ it_behaves_like 'raises and logs error' do
+ let(:deletion) { true }
end
end
@@ -29,14 +40,23 @@ RSpec.describe Gitlab::Checks::SnippetCheck do
let(:oldrev) { '0000000000000000000000000000000000000000' }
let(:ref) { 'refs/heads/feature' }
- it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can not create or delete branches.')
+ it_behaves_like 'raises and logs error' do
+ let(:creation) { true }
+ let(:branch_name) { 'feature' }
end
- context "when branch is 'master'" do
- let(:ref) { 'refs/heads/master' }
+ context 'when branch is the same as the default branch' do
+ let(:ref) { "refs/heads/#{default_branch}" }
- it "allows the operation" do
+ it 'allows the operation' do
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+
+ context 'when snippet has an empty repo' do
+ let_it_be(:snippet) { create(:personal_snippet, :empty_repo) }
+
+ it 'allows the operation' do
expect { subject.validate! }.not_to raise_error
end
end
@@ -45,8 +65,8 @@ RSpec.describe Gitlab::Checks::SnippetCheck do
context 'when default_branch is nil' do
let(:default_branch) { nil }
- it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can not create or delete branches.')
+ it_behaves_like 'raises and logs error' do
+ let(:branch_name) { 'master' }
end
end
end
diff --git a/spec/lib/gitlab/ci/ansi2json/result_spec.rb b/spec/lib/gitlab/ci/ansi2json/result_spec.rb
index 31c0da95f0a..b7b4d6de8b9 100644
--- a/spec/lib/gitlab/ci/ansi2json/result_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/result_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Gitlab::Ci::Ansi2json::Result do
{ lines: [], state: state, append: false, truncated: false, offset: offset, stream: stream }
end
- subject { described_class.new(params) }
+ subject { described_class.new(**params) }
describe '#size' do
before do
diff --git a/spec/lib/gitlab/ci/ansi2json/style_spec.rb b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
index d27a642ecf3..ff70ff69aaa 100644
--- a/spec/lib/gitlab/ci/ansi2json/style_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Ansi2json::Style do
describe '#set?' do
- subject { described_class.new(params).set? }
+ subject { described_class.new(**params).set? }
context 'when fg color is set' do
let(:params) { { fg: 'term-fg-black' } }
@@ -44,7 +44,7 @@ RSpec.describe Gitlab::Ci::Ansi2json::Style do
end
describe 'update formats to mimic terminals' do
- subject { described_class.new(params) }
+ subject { described_class.new(**params) }
context 'when fg color present' do
let(:params) { { fg: 'term-fg-black', mask: mask } }
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index 77b8aa1d591..efe99cd276c 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -142,7 +142,7 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata do
it 'reads expected number of entries' do
stream = File.open(tmpfile.path)
- metadata = described_class.new(stream, 'public', { recursive: true })
+ metadata = described_class.new(stream, 'public', recursive: true)
expect(metadata.find_entries!.count).to eq entry_count
end
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause_spec.rb
new file mode 100644
index 00000000000..faede7a361f
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause do
+ describe '.fabricate' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:value) { 'some value' }
+
+ subject { described_class.fabricate(type, value) }
+
+ context 'when type is valid' do
+ where(:type, :result) do
+ 'changes' | Gitlab::Ci::Build::Rules::Rule::Clause::Changes
+ 'exists' | Gitlab::Ci::Build::Rules::Rule::Clause::Exists
+ 'if' | Gitlab::Ci::Build::Rules::Rule::Clause::If
+ end
+
+ with_them do
+ it { is_expected.to be_instance_of(result) }
+ end
+ end
+
+ context 'when type is invalid' do
+ let(:type) { 'when' }
+
+ it { is_expected.to be_nil }
+
+ context "when type is 'variables'" do
+ let(:type) { 'variables' }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb
index cbeae33fbcf..a1af5b75f87 100644
--- a/spec/lib/gitlab/ci/build/rules_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules_spec.rb
@@ -104,7 +104,7 @@ RSpec.describe Gitlab::Ci::Build::Rules do
context 'with one rule without any clauses' do
let(:rule_list) { [{ when: 'manual', allow_failure: true }] }
- it { is_expected.to eq(described_class::Result.new('manual', nil, true)) }
+ it { is_expected.to eq(described_class::Result.new('manual', nil, true, nil)) }
end
context 'with one matching rule' do
@@ -171,7 +171,7 @@ RSpec.describe Gitlab::Ci::Build::Rules do
context 'with matching rule' do
let(:rule_list) { [{ if: '$VAR == null', allow_failure: true }] }
- it { is_expected.to eq(described_class::Result.new('on_success', nil, true)) }
+ it { is_expected.to eq(described_class::Result.new('on_success', nil, true, nil)) }
end
context 'with non-matching rule' do
@@ -180,18 +180,60 @@ RSpec.describe Gitlab::Ci::Build::Rules do
it { is_expected.to eq(described_class::Result.new('never')) }
end
end
+
+ context 'with variables' do
+ context 'with matching rule' do
+ let(:rule_list) { [{ if: '$VAR == null', variables: { MY_VAR: 'my var' } }] }
+
+ it { is_expected.to eq(described_class::Result.new('on_success', nil, nil, { MY_VAR: 'my var' })) }
+ end
+ end
end
describe 'Gitlab::Ci::Build::Rules::Result' do
let(:when_value) { 'on_success' }
let(:start_in) { nil }
let(:allow_failure) { nil }
+ let(:variables) { nil }
- subject { Gitlab::Ci::Build::Rules::Result.new(when_value, start_in, allow_failure) }
+ subject(:result) do
+ Gitlab::Ci::Build::Rules::Result.new(when_value, start_in, allow_failure, variables)
+ end
describe '#build_attributes' do
+ let(:seed_attributes) { {} }
+
+ subject(:build_attributes) do
+ result.build_attributes(seed_attributes)
+ end
+
it 'compacts nil values' do
- expect(subject.build_attributes).to eq(options: {}, when: 'on_success')
+ is_expected.to eq(options: {}, when: 'on_success')
+ end
+
+ context 'when there are variables in rules' do
+ let(:variables) { { VAR1: 'new var 1', VAR3: 'var 3' } }
+
+ context 'when there are seed variables' do
+ let(:seed_attributes) do
+ { yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true }] }
+ end
+
+ it 'returns yaml_variables with override' do
+ is_expected.to include(
+ yaml_variables: [{ key: 'VAR1', value: 'new var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }]
+ )
+ end
+ end
+
+ context 'when there is not seed variables' do
+ it 'does not return yaml_variables' do
+ is_expected.not_to have_key(:yaml_variables)
+ end
+ end
end
end
@@ -200,7 +242,7 @@ RSpec.describe Gitlab::Ci::Build::Rules do
let!(:when_value) { 'never' }
it 'returns false' do
- expect(subject.pass?).to eq(false)
+ expect(result.pass?).to eq(false)
end
end
@@ -208,7 +250,7 @@ RSpec.describe Gitlab::Ci::Build::Rules do
let!(:when_value) { 'on_success' }
it 'returns true' do
- expect(subject.pass?).to eq(true)
+ expect(result.pass?).to eq(true)
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/allow_failure_spec.rb b/spec/lib/gitlab/ci/config/entry/allow_failure_spec.rb
new file mode 100644
index 00000000000..7aaad57f5cd
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/allow_failure_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Entry::AllowFailure do
+ let(:entry) { described_class.new(config.deep_dup) }
+ let(:expected_config) { config }
+
+ describe 'validations' do
+ context 'when entry config value is valid' do
+ shared_examples 'valid entry' do
+ describe '#value' do
+ it 'returns key value' do
+ expect(entry.value).to eq(expected_config)
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'with boolean values' do
+ it_behaves_like 'valid entry' do
+ let(:config) { true }
+ end
+
+ it_behaves_like 'valid entry' do
+ let(:config) { false }
+ end
+ end
+
+ context 'with hash values' do
+ it_behaves_like 'valid entry' do
+ let(:config) { { exit_codes: 137 } }
+ let(:expected_config) { { exit_codes: [137] } }
+ end
+
+ it_behaves_like 'valid entry' do
+ let(:config) { { exit_codes: [42, 137] } }
+ end
+ end
+ end
+
+ context 'when entry value is not valid' do
+ shared_examples 'invalid entry' do
+ describe '#valid?' do
+ it { expect(entry).not_to be_valid }
+ it { expect(entry.errors).to include(error_message) }
+ end
+ end
+
+ context 'when it has a wrong type' do
+ let(:config) { [1] }
+ let(:error_message) do
+ 'allow failure config should be a hash or a boolean value'
+ end
+
+ it_behaves_like 'invalid entry'
+ end
+
+ context 'with string exit codes' do
+ let(:config) { { exit_codes: 'string' } }
+ let(:error_message) do
+ 'allow failure exit codes should be an array of integers or an integer'
+ end
+
+ it_behaves_like 'invalid entry'
+ end
+
+ context 'with array of strings as exit codes' do
+ let(:config) { { exit_codes: ['string 1', 'string 2'] } }
+ let(:error_message) do
+ 'allow failure exit codes should be an array of integers or an integer'
+ end
+
+ it_behaves_like 'invalid entry'
+ end
+
+ context 'when it has an extra keys' do
+ let(:config) { { extra: true } }
+ let(:error_message) do
+ 'allow failure config contains unknown keys: extra'
+ end
+
+ it_behaves_like 'invalid entry'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index 8b2e0410474..b3b7901074a 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -227,6 +227,23 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
end
end
end
+
+ context 'when bridge config contains exit_codes' do
+ let(:config) do
+ { script: 'rspec', allow_failure: { exit_codes: [42] } }
+ end
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns an error message' do
+ expect(subject.errors)
+ .to include(/allow failure should be a boolean value/)
+ end
+ end
+ end
end
describe '#manual_action?' do
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index c3d91057328..e810d65d560 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -81,7 +81,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
context 'when configuration has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
let(:config) { { name: 'ruby:2.7', entrypoint: %w(/bin/sh run), ports: ports } }
- let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:entry) { described_class.new(config, with_image_ports: image_ports) }
let(:image_ports) { false }
context 'when with_image_ports metadata is not enabled' do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index e0e8bc93770..7834a1a94f2 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -670,6 +670,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
end
describe '#ignored?' do
+ before do
+ entry.compose!
+ end
+
context 'when job is a manual action' do
context 'when it is not specified if job is allowed to fail' do
let(:config) do
@@ -700,6 +704,16 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
expect(entry).not_to be_ignored
end
end
+
+ context 'when job is dynamically allowed to fail' do
+ let(:config) do
+ { script: 'deploy', when: 'manual', allow_failure: { exit_codes: 42 } }
+ end
+
+ it 'is not an ignored job' do
+ expect(entry).not_to be_ignored
+ end
+ end
end
context 'when job is not a manual action' do
@@ -709,6 +723,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
+
+ it 'does not return allow_failure' do
+ expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
+ end
end
context 'when job is allowed to fail' do
@@ -717,6 +735,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'is an ignored job' do
expect(entry).to be_ignored
end
+
+ it 'does not return allow_failure_criteria' do
+ expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
+ end
end
context 'when job is not allowed to fail' do
@@ -725,6 +747,32 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
+
+ it 'does not return allow_failure_criteria' do
+ expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
+ end
+ end
+
+ context 'when job is dynamically allowed to fail' do
+ let(:config) { { script: 'deploy', allow_failure: { exit_codes: 42 } } }
+
+ it 'is not an ignored job' do
+ expect(entry).not_to be_ignored
+ end
+
+ it 'returns allow_failure_criteria' do
+ expect(entry.value[:allow_failure_criteria]).to match(exit_codes: [42])
+ end
+
+ context 'with ci_allow_failure_with_exit_codes disabled' do
+ before do
+ stub_feature_flags(ci_allow_failure_with_exit_codes: false)
+ end
+
+ it 'does not return allow_failure_criteria' do
+ expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/need_spec.rb b/spec/lib/gitlab/ci/config/entry/need_spec.rb
index 5a826bf8282..983e95fae42 100644
--- a/spec/lib/gitlab/ci/config/entry/need_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb
@@ -165,6 +165,45 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
end
end
+ context 'with cross pipeline artifacts needs' do
+ context 'when pipeline is provided' do
+ context 'when job is provided' do
+ let(:config) { { job: 'job_name', pipeline: '$THE_PIPELINE_ID' } }
+
+ it { is_expected.to be_valid }
+
+ it 'sets artifacts:true by default' do
+ expect(need.value).to eq(job: 'job_name', pipeline: '$THE_PIPELINE_ID', artifacts: true)
+ end
+
+ it 'sets the type as cross_dependency' do
+ expect(need.type).to eq(:cross_dependency)
+ end
+ end
+
+ context 'when artifacts is provided' do
+ let(:config) { { job: 'job_name', pipeline: '$THE_PIPELINE_ID', artifacts: false } }
+
+ it { is_expected.to be_valid }
+
+ it 'returns the correct value' do
+ expect(need.value).to eq(job: 'job_name', pipeline: '$THE_PIPELINE_ID', artifacts: false)
+ end
+ end
+ end
+
+ context 'when config contains not allowed keys' do
+ let(:config) { { job: 'job_name', pipeline: '$THE_PIPELINE_ID', something: 'else' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error' do
+ expect(need.errors)
+ .to contain_exactly('cross pipeline dependency config contains unknown keys: something')
+ end
+ end
+ end
+
context 'when need config is not a string or a hash' do
let(:config) { :job_name }
diff --git a/spec/lib/gitlab/ci/config/entry/needs_spec.rb b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
index f3b9d0c3c84..f11f2a56f5f 100644
--- a/spec/lib/gitlab/ci/config/entry/needs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
subject(:needs) { described_class.new(config) }
before do
- needs.metadata[:allowed_needs] = %i[job]
+ needs.metadata[:allowed_needs] = %i[job cross_dependency]
end
describe 'validations' do
@@ -66,6 +66,27 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
end
end
end
+
+ context 'with too many cross pipeline dependencies' do
+ let(:limit) { described_class::NEEDS_CROSS_PIPELINE_DEPENDENCIES_LIMIT }
+
+ let(:config) do
+ Array.new(limit.next) do |index|
+ { pipeline: "$UPSTREAM_PIPELINE_#{index}", job: 'job-1' }
+ end
+ end
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns error about incorrect type' do
+ expect(needs.errors).to contain_exactly(
+ "needs config must be less than or equal to #{limit}")
+ end
+ end
+ end
end
describe '.compose!' do
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index ac8dd2a3267..aadf94365c6 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -361,7 +361,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
context 'when root yaml variables are used' do
let(:variables) do
Gitlab::Ci::Config::Entry::Variables.new(
- A: 'root', C: 'root', D: 'root'
+ { A: 'root', C: 'root', D: 'root' }
).value
end
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 79716df6b60..54c7a5c3602 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
image: 'ruby:2.7',
default: {},
services: ['postgres:9.1', 'mysql:5.5'],
- variables: { VAR: 'root' },
+ variables: { VAR: 'root', VAR2: { value: 'val 2', description: 'this is var 2' } },
after_script: ['make clean'],
stages: %w(build pages release),
cache: { key: 'k', untracked: true, paths: ['public/'] },
@@ -80,6 +80,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
.to eq 'List of external YAML files to include.'
end
+ it 'sets correct variables value' do
+ expect(root.variables_value).to eq('VAR' => 'root', 'VAR2' => 'val 2')
+ end
+
describe '#leaf?' do
it 'is not leaf' do
expect(root).not_to be_leaf
@@ -128,7 +132,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
- variables: { 'VAR' => 'root' },
+ variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -142,7 +146,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
- variables: { 'VAR' => 'root' },
+ variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -158,7 +162,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' },
only: { refs: %w(branches tags) },
- variables: { 'VAR' => 'job' },
+ variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
after_script: [],
ignore: false,
scheduling_type: :stage }
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
index 4a43e6c9a86..d1bd22e5573 100644
--- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
@@ -339,6 +339,22 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do
end
end
end
+
+ context 'with an invalid variables' do
+ let(:config) do
+ { if: '$THIS == "that"', variables: 'hello' }
+ end
+
+ before do
+ subject.compose!
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error about invalid variables:' do
+ expect(subject.errors).to include(/variables config should be a hash of key value pairs/)
+ end
+ end
end
context 'allow_failure: validation' do
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index ec137ef2ae4..2795cc9dddf 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -96,7 +96,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
{ name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports }
end
- let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:entry) { described_class.new(config, with_image_ports: image_ports) }
let(:image_ports) { false }
context 'when with_image_ports metadata is not enabled' do
diff --git a/spec/lib/gitlab/ci/config/entry/services_spec.rb b/spec/lib/gitlab/ci/config/entry/services_spec.rb
index e4f8a348d21..85e7f297b03 100644
--- a/spec/lib/gitlab/ci/config/entry/services_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/services_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Services do
context 'when configuration has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
let(:config) { ['postgresql:9.5', { name: 'postgresql:9.1', alias: 'postgres_old', ports: ports }] }
- let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:entry) { described_class.new(config, with_image_ports: image_ports) }
let(:image_ports) { false }
context 'when with_image_ports metadata is not enabled' do
diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
index ac33f858f43..426a38e2ef7 100644
--- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
@@ -3,7 +3,9 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Variables do
- subject { described_class.new(config) }
+ let(:metadata) { {} }
+
+ subject { described_class.new(config, metadata) }
shared_examples 'valid config' do
describe '#value' do
@@ -71,7 +73,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do
{ 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' }
end
- it_behaves_like 'valid config'
+ it_behaves_like 'invalid config'
+
+ context 'when metadata has use_value_data' do
+ let(:metadata) { { use_value_data: true } }
+
+ it_behaves_like 'valid config'
+ end
end
context 'when entry value is an array' do
@@ -80,32 +88,36 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do
it_behaves_like 'invalid config'
end
- context 'when entry value has hash with other key-pairs' do
- let(:config) do
- { 'VARIABLE_1' => { value: 'value 1', hello: 'variable 1' },
- 'VARIABLE_2' => 'value 2' }
- end
+ context 'when metadata has use_value_data' do
+ let(:metadata) { { use_value_data: true } }
- it_behaves_like 'invalid config'
- end
+ context 'when entry value has hash with other key-pairs' do
+ let(:config) do
+ { 'VARIABLE_1' => { value: 'value 1', hello: 'variable 1' },
+ 'VARIABLE_2' => 'value 2' }
+ end
- context 'when entry config value has hash with nil description' do
- let(:config) do
- { 'VARIABLE_1' => { value: 'value 1', description: nil } }
+ it_behaves_like 'invalid config'
end
- it_behaves_like 'invalid config'
- end
+ context 'when entry config value has hash with nil description' do
+ let(:config) do
+ { 'VARIABLE_1' => { value: 'value 1', description: nil } }
+ end
- context 'when entry config value has hash without description' do
- let(:config) do
- { 'VARIABLE_1' => { value: 'value 1' } }
+ it_behaves_like 'invalid config'
end
- let(:result) do
- { 'VARIABLE_1' => 'value 1' }
- end
+ context 'when entry config value has hash without description' do
+ let(:config) do
+ { 'VARIABLE_1' => { value: 'value 1' } }
+ end
- it_behaves_like 'valid config'
+ let(:result) do
+ { 'VARIABLE_1' => 'value 1' }
+ end
+
+ it_behaves_like 'valid config'
+ end
end
end
diff --git a/spec/lib/gitlab/ci/mask_secret_spec.rb b/spec/lib/gitlab/ci/mask_secret_spec.rb
index 7b2d6b58518..7d950c86700 100644
--- a/spec/lib/gitlab/ci/mask_secret_spec.rb
+++ b/spec/lib/gitlab/ci/mask_secret_spec.rb
@@ -22,6 +22,10 @@ RSpec.describe Gitlab::Ci::MaskSecret do
expect(mask('token', nil)).to eq('token')
end
+ it 'does not change a bytesize of a value' do
+ expect(mask('token-ü/unicode', 'token-ü').bytesize).to eq 16
+ end
+
def mask(value, token)
subject.mask!(value.dup, token)
end
diff --git a/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb b/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb
new file mode 100644
index 00000000000..c6b8cf2a985
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Parsers::Codequality::CodeClimate do
+ describe '#parse!' do
+ subject(:parse) { described_class.new.parse!(code_climate, codequality_report) }
+
+ let(:codequality_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:code_climate) do
+ [
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }
+ ].to_json
+ end
+
+ context "when data is code_climate style JSON" do
+ context "when there are no degradations" do
+ let(:code_climate) { [].to_json }
+
+ it "returns a codequality report" do
+ expect { parse }.not_to raise_error
+
+ expect(codequality_report.degradations_count).to eq(0)
+ end
+ end
+
+ context "when there are degradations" do
+ it "returns a codequality report" do
+ expect { parse }.not_to raise_error
+
+ expect(codequality_report.degradations_count).to eq(1)
+ end
+ end
+ end
+
+ context "when data is not a valid JSON string" do
+ let(:code_climate) do
+ [
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }
+ ]
+ end
+
+ it "sets error_message" do
+ expect { parse }.not_to raise_error
+
+ expect(codequality_report.error_message).to include('JSON parsing failed')
+ end
+ end
+
+ context 'when degradations contain an invalid one' do
+ let(:code_climate) do
+ [
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "fingerprint": "ab5f8b935886b942d621399aefkaehfiaehf",
+ "severity": "minor"
+ },
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }
+ ].to_json
+ end
+
+ it 'stops parsing the report' do
+ expect { parse }.not_to raise_error
+
+ expect(codequality_report.degradations_count).to eq(0)
+ expect(codequality_report.error_message).to eq("Invalid degradation format: The property '#/' did not contain a required property of 'location'")
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
index 45e87466532..2313378d1e9 100644
--- a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
@@ -4,207 +4,690 @@ require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Parsers::Coverage::Cobertura do
describe '#parse!' do
- subject { described_class.new.parse!(cobertura, coverage_report) }
+ subject(:parse_report) { described_class.new.parse!(cobertura, coverage_report, project_path: project_path, worktree_paths: paths) }
let(:coverage_report) { Gitlab::Ci::Reports::CoverageReports.new }
+ let(:project_path) { 'foo/bar' }
+ let(:paths) { ['app/user.rb'] }
+
+ let(:cobertura) do
+ <<~EOF
+ <coverage>
+ #{sources_xml}
+ #{classes_xml}
+ </coverage>
+ EOF
+ end
context 'when data is Cobertura style XML' do
- context 'when there is no <class>' do
- let(:cobertura) { '' }
+ shared_examples_for 'ignoring sources, project_path, and worktree_paths' do
+ context 'when there is no <class>' do
+ let(:classes_xml) { '' }
- it 'parses XML and returns empty coverage' do
- expect { subject }.not_to raise_error
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
- expect(coverage_report.files).to eq({})
+ expect(coverage_report.files).to eq({})
+ end
end
- end
- context 'when there is a <sources>' do
- shared_examples_for 'ignoring sources' do
- it 'parses XML without errors' do
- expect { subject }.not_to raise_error
+ context 'when there is a single <class>' do
+ context 'with no lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
+ end
- expect(coverage_report.files).to eq({})
+ context 'with a single line' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } })
+ end
+ end
+
+ context 'without a package parent' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages>
+ <class filename="app.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } })
+ end
+ end
+
+ context 'with multiple lines and methods info' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
+ end
end
end
- context 'and has a single source' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
+ context 'when there are multiple <class>' do
+ context 'without a package parent' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ <class filename="foo.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ </lines></class>
+ </packages>
+ EOF
+ end
+
+ it 'parses XML and returns coverage information per class' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 }, 'foo.rb' => { 6 => 1 } })
+ end
+ end
+
+ context 'with the same filename and different lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="app.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with merged coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
+ end
+ end
+
+ context 'with the same filename and lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="1"/>
+ <line number="2" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with summed-up coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 3, 2 => 1 } })
+ end
+ end
+
+ context 'with missing filename' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and ignores class with missing name' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
+ end
+ end
+
+ context 'with invalid line information' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="app.rb"><methods/><lines>
+ <line null="test" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'raises an error' do
+ expect { parse_report }.to raise_error(described_class::InvalidLineInformationError)
+ end
+ end
+ end
+ end
+
+ context 'when there is no <sources>' do
+ let(:sources_xml) { '' }
+
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
+ end
+
+ context 'when there is a <sources>' do
+ context 'and has a single source with a pattern for Go projects' do
+ let(:project_path) { 'local/go' } # Make sure we're not making false positives
+ let(:sources_xml) do
+ <<~EOF
<sources>
- <source>project/src</source>
+ <source>/usr/local/go/src</source>
</sources>
EOF
end
- it_behaves_like 'ignoring sources'
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
end
- context 'and has multiple sources' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
+ context 'and has multiple sources with a pattern for Go projects' do
+ let(:project_path) { 'local/go' } # Make sure we're not making false positives
+ let(:sources_xml) do
+ <<~EOF
<sources>
- <source>project/src/foo</source>
- <source>project/src/bar</source>
+ <source>/usr/local/go/src</source>
+ <source>/go/src</source>
</sources>
EOF
end
- it_behaves_like 'ignoring sources'
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
end
- end
- context 'when there is a single <class>' do
- context 'with no lines' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes><class filename="app.rb"></class></classes>
+ context 'and has a single source but already is at the project root path' do
+ let(:sources_xml) do
+ <<~EOF
+ <sources>
+ <source>builds/#{project_path}</source>
+ </sources>
EOF
end
- it 'parses XML and returns empty coverage' do
- expect { subject }.not_to raise_error
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
+ end
- expect(coverage_report.files).to eq({})
+ context 'and has multiple sources but already are at the project root path' do
+ let(:sources_xml) do
+ <<~EOF
+ <sources>
+ <source>builds/#{project_path}/</source>
+ <source>builds/somewhere/#{project_path}</source>
+ </sources>
+ EOF
end
+
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
end
- context 'with a single line' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes>
- <class filename="app.rb"><lines>
- <line number="1" hits="2"/>
- </lines></class>
- </classes>
+ context 'and has a single source that is not at the project root path' do
+ let(:sources_xml) do
+ <<~EOF
+ <sources>
+ <source>builds/#{project_path}/app</source>
+ </sources>
EOF
end
- it 'parses XML and returns a single file with coverage' do
- expect { subject }.not_to raise_error
+ context 'when there is no <class>' do
+ let(:classes_xml) { '' }
- expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } })
- end
- end
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
- context 'with multipe lines and methods info' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="2"/>
- <line number="2" hits="0"/>
- </lines></class>
- </classes>
- EOF
+ expect(coverage_report.files).to eq({})
+ end
end
- it 'parses XML and returns a single file with coverage' do
- expect { subject }.not_to raise_error
+ context 'when there is a single <class>' do
+ context 'with no lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
+ end
+
+ context 'with a single line but the filename cannot be determined based on extracted source and worktree paths' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="member.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
+ end
+
+ context 'with a single line' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2 } })
+ end
+ end
+
+ context 'with multiple lines and methods info' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } })
+ end
+ end
+ end
- expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
+ context 'when there are multiple <class>' do
+ context 'with the same filename but the filename cannot be determined based on extracted source and worktree paths' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="member.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="member.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
+ end
+
+ context 'without a parent package' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="user.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </packages>
+ EOF
+ end
+
+ it 'parses XML and returns coverage information with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
+ end
+ end
+
+ context 'with the same filename and different lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="user.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with merged coverage, and with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
+ end
+ end
+
+ context 'with the same filename and lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="1"/>
+ <line number="2" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with summed-up coverage, and with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 3, 2 => 1 } })
+ end
+ end
+
+ context 'with missing filename' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and ignores class with missing name' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } })
+ end
+ end
+
+ context 'with filename that cannot be determined based on extracted source and worktree paths' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="member.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and ignores class with undetermined filename' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } })
+ end
+ end
+
+ context 'with invalid line information' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="user.rb"><methods/><lines>
+ <line null="test" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'raises an error' do
+ expect { parse_report }.to raise_error(described_class::InvalidLineInformationError)
+ end
+ end
end
end
- end
- context 'when there are multipe <class>' do
- context 'with the same filename and different lines' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="2"/>
- <line number="2" hits="0"/>
- </lines></class>
- <class filename="app.rb"><methods/><lines>
- <line number="6" hits="1"/>
- <line number="7" hits="1"/>
- </lines></class>
- </classes>
+ context 'and has multiple sources that are not at the project root path' do
+ let(:sources_xml) do
+ <<~EOF
+ <sources>
+ <source>builds/#{project_path}/app1/</source>
+ <source>builds/#{project_path}/app2/</source>
+ </sources>
EOF
end
- it 'parses XML and returns a single file with merged coverage' do
- expect { subject }.not_to raise_error
-
- expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
+ context 'and a class filename is available under multiple extracted sources' do
+ let(:paths) { ['app1/user.rb', 'app2/user.rb'] }
+
+ let(:classes_xml) do
+ <<~EOF
+ <package name="app1">
+ <classes>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes>
+ </package>
+ <package name="app2">
+ <classes>
+ <class filename="user.rb"><lines>
+ <line number="2" hits="3"/>
+ </lines></class>
+ </classes>
+ </package>
+ EOF
+ end
+
+ it 'parses XML and returns the files with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({
+ 'app1/user.rb' => { 1 => 2 },
+ 'app2/user.rb' => { 2 => 3 }
+ })
+ end
end
- end
- context 'with the same filename and lines' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <packages><package><classes>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="2"/>
- <line number="2" hits="0"/>
- </lines></class>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="1"/>
- <line number="2" hits="1"/>
- </lines></class>
- </classes></package></packages>
- EOF
+ context 'and a class filename is available under one of the extracted sources' do
+ let(:paths) { ['app1/member.rb', 'app2/user.rb', 'app2/pet.rb'] }
+
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with the filename relative to project root using the extracted source where it is first found under' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app2/user.rb' => { 1 => 2 } })
+ end
end
- it 'parses XML and returns a single file with summed-up coverage' do
- expect { subject }.not_to raise_error
+ context 'and a class filename is not found under any of the extracted sources' do
+ let(:paths) { ['app1/member.rb', 'app2/pet.rb'] }
- expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 3, 2 => 1 } })
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
end
- end
- context 'with missing filename' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="2"/>
- <line number="2" hits="0"/>
- </lines></class>
- <class><methods/><lines>
- <line number="6" hits="1"/>
- <line number="7" hits="1"/>
- </lines></class>
- </classes>
- EOF
+ context 'and a class filename is not found under any of the extracted sources within the iteratable limit' do
+ let(:paths) { ['app2/user.rb'] }
+
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="record.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ before do
+ stub_const("#{described_class}::MAX_SOURCES", 1)
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
end
+ end
+ end
- it 'parses XML and ignores class with missing name' do
- expect { subject }.not_to raise_error
+ shared_examples_for 'non-smart parsing' do
+ let(:sources_xml) do
+ <<~EOF
+ <sources>
+ <source>builds/foo/bar/app</source>
+ </sources>
+ EOF
+ end
- expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
- end
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
end
- context 'with invalid line information' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="2"/>
- <line number="2" hits="0"/>
- </lines></class>
- <class filename="app.rb"><methods/><lines>
- <line null="test" hits="1"/>
- <line number="7" hits="1"/>
- </lines></class>
- </classes>
- EOF
- end
+ it 'parses XML and returns filenames unchanged just as how they are found in the class node' do
+ expect { parse_report }.not_to raise_error
- it 'raises an error' do
- expect { subject }.to raise_error(described_class::CoberturaParserError)
- end
+ expect(coverage_report.files).to eq({ 'user.rb' => { 1 => 2 } })
end
end
+
+ context 'when project_path is not present' do
+ let(:project_path) { nil }
+ let(:paths) { ['app/user.rb'] }
+
+ it_behaves_like 'non-smart parsing'
+ end
+
+ context 'when worktree_paths is not present' do
+ let(:project_path) { 'foo/bar' }
+ let(:paths) { nil }
+
+ it_behaves_like 'non-smart parsing'
+ end
end
context 'when data is not Cobertura style XML' do
let(:cobertura) { { coverage: '12%' }.to_json }
it 'raises an error' do
- expect { subject }.to raise_error(described_class::CoberturaParserError)
+ expect { parse_report }.to raise_error(described_class::InvalidXMLError)
end
end
end
diff --git a/spec/lib/gitlab/ci/parsers_spec.rb b/spec/lib/gitlab/ci/parsers_spec.rb
index db9a5775d9f..b932cd81272 100644
--- a/spec/lib/gitlab/ci/parsers_spec.rb
+++ b/spec/lib/gitlab/ci/parsers_spec.rb
@@ -30,6 +30,14 @@ RSpec.describe Gitlab::Ci::Parsers do
end
end
+ context 'when file_type is codequality' do
+ let(:file_type) { 'codequality' }
+
+ it 'fabricates the class' do
+ is_expected.to be_a(described_class::Codequality::CodeClimate)
+ end
+ end
+
context 'when file_type is terraform' do
let(:file_type) { 'terraform' }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
new file mode 100644
index 00000000000..78363be7f36
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
+ let_it_be(:namespace) { create(:namespace) }
+ let_it_be(:project, reload: true) { create(:project, namespace: namespace) }
+ let_it_be(:plan_limits, reload: true) { create(:plan_limits, :default_plan) }
+
+ let(:pipeline_seed) { double(:pipeline_seed, deployments_count: 2) }
+ let(:save_incompleted) { false }
+
+ let(:command) do
+ double(:command,
+ project: project,
+ pipeline_seed: pipeline_seed,
+ save_incompleted: save_incompleted
+ )
+ end
+
+ let(:pipeline) { build(:ci_pipeline, project: project) }
+ let(:step) { described_class.new(pipeline, command) }
+
+ subject(:perform) { step.perform! }
+
+ context 'when pipeline deployments limit is exceeded' do
+ before do
+ plan_limits.update!(ci_pipeline_deployments: 1)
+ end
+
+ context 'when saving incompleted pipelines' do
+ let(:save_incompleted) { true }
+
+ it 'drops the pipeline' do
+ perform
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.reload).to be_failed
+ end
+
+ it 'breaks the chain' do
+ perform
+
+ expect(step.break?).to be true
+ end
+
+ it 'sets a valid failure reason' do
+ perform
+
+ expect(pipeline.deployments_limit_exceeded?).to be true
+ end
+ end
+
+ context 'when not saving incomplete pipelines' do
+ let(:save_incompleted) { false }
+
+ it 'does not persist the pipeline' do
+ perform
+
+ expect(pipeline).not_to be_persisted
+ end
+
+ it 'breaks the chain' do
+ perform
+
+ expect(step.break?).to be true
+ end
+
+ it 'adds an informative error to the pipeline' do
+ perform
+
+ expect(pipeline.errors.messages).to include(base: ['Pipeline has too many deployments! Requested 2, but the limit is 1.'])
+ end
+ end
+
+ it 'logs the error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ instance_of(Gitlab::Ci::Limit::LimitExceededError),
+ project_id: project.id, plan: namespace.actual_plan_name
+ )
+
+ perform
+ end
+ end
+
+ context 'when pipeline deployments limit is not exceeded' do
+ before do
+ plan_limits.update!(ci_pipeline_deployments: 100)
+ end
+
+ it 'does not break the chain' do
+ perform
+
+ expect(step.break?).to be false
+ end
+
+ it 'does not invalidate the pipeline' do
+ perform
+
+ expect(pipeline.errors).to be_empty
+ end
+
+ it 'does not log any error' do
+ expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
+
+ perform
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
index d849c768a3c..0ce8b80902e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
@@ -50,8 +50,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
it 'sets the seeds in the command object' do
run_chain
- expect(command.stage_seeds).to all(be_a Gitlab::Ci::Pipeline::Seed::Base)
- expect(command.stage_seeds.count).to eq 1
+ expect(command.pipeline_seed).to be_a(Gitlab::Ci::Pipeline::Seed::Pipeline)
+ expect(command.pipeline_seed.size).to eq 1
end
context 'when no ref policy is specified' do
@@ -63,16 +63,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
}
end
- it 'correctly fabricates a stage seeds object' do
+ it 'correctly fabricates stages and builds' do
run_chain
- seeds = command.stage_seeds
- expect(seeds.size).to eq 2
- expect(seeds.first.attributes[:name]).to eq 'test'
- expect(seeds.second.attributes[:name]).to eq 'deploy'
- expect(seeds.dig(0, 0, :name)).to eq 'rspec'
- expect(seeds.dig(0, 1, :name)).to eq 'spinach'
- expect(seeds.dig(1, 0, :name)).to eq 'production'
+ seed = command.pipeline_seed
+
+ expect(seed.stages.size).to eq 2
+ expect(seed.size).to eq 3
+ expect(seed.stages.first.name).to eq 'test'
+ expect(seed.stages.second.name).to eq 'deploy'
+ expect(seed.stages[0].statuses[0].name).to eq 'rspec'
+ expect(seed.stages[0].statuses[1].name).to eq 'spinach'
+ expect(seed.stages[1].statuses[0].name).to eq 'production'
end
end
@@ -88,14 +90,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
}
end
- it 'returns stage seeds only assigned to master' do
+ it 'returns pipeline seed with jobs only assigned to master' do
run_chain
- seeds = command.stage_seeds
+ seed = command.pipeline_seed
- expect(seeds.size).to eq 1
- expect(seeds.first.attributes[:name]).to eq 'test'
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+ expect(seed.size).to eq 1
+ expect(seed.stages.first.name).to eq 'test'
+ expect(seed.stages[0].statuses[0].name).to eq 'spinach'
end
end
@@ -109,14 +111,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
}
end
- it 'returns stage seeds only assigned to schedules' do
+ it 'returns pipeline seed with jobs only assigned to schedules' do
run_chain
- seeds = command.stage_seeds
+ seed = command.pipeline_seed
- expect(seeds.size).to eq 1
- expect(seeds.first.attributes[:name]).to eq 'test'
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+ expect(seed.size).to eq 1
+ expect(seed.stages.first.name).to eq 'test'
+ expect(seed.stages[0].statuses[0].name).to eq 'spinach'
end
end
@@ -141,11 +143,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
it 'returns seeds for kubernetes dependent job' do
run_chain
- seeds = command.stage_seeds
+ seed = command.pipeline_seed
- expect(seeds.size).to eq 2
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
- expect(seeds.dig(1, 0, :name)).to eq 'production'
+ expect(seed.size).to eq 2
+ expect(seed.stages[0].statuses[0].name).to eq 'spinach'
+ expect(seed.stages[1].statuses[0].name).to eq 'production'
end
end
end
@@ -154,10 +156,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
it 'does not return seeds for kubernetes dependent job' do
run_chain
- seeds = command.stage_seeds
+ seed = command.pipeline_seed
- expect(seeds.size).to eq 1
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+ expect(seed.size).to eq 1
+ expect(seed.stages[0].statuses[0].name).to eq 'spinach'
end
end
end
@@ -173,10 +175,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
it 'returns stage seeds only when variables expression is truthy' do
run_chain
- seeds = command.stage_seeds
+ seed = command.pipeline_seed
- expect(seeds.size).to eq 1
- expect(seeds.dig(0, 0, :name)).to eq 'unit'
+ expect(seed.size).to eq 1
+ expect(seed.stages[0].statuses[0].name).to eq 'unit'
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb
new file mode 100644
index 00000000000..c52994fc6a2
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Quota::Deployments do
+ let_it_be(:namespace) { create(:namespace) }
+ let_it_be(:default_plan, reload: true) { create(:default_plan) }
+ let_it_be(:project, reload: true) { create(:project, :repository, namespace: namespace) }
+ let_it_be(:plan_limits) { create(:plan_limits, plan: default_plan) }
+
+ let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
+
+ let(:pipeline_seed) { double(:pipeline_seed, deployments_count: 2)}
+
+ let(:command) do
+ double(:command,
+ project: project,
+ pipeline_seed: pipeline_seed,
+ save_incompleted: true
+ )
+ end
+
+ let(:ci_pipeline_deployments_limit) { 0 }
+
+ before do
+ plan_limits.update!(ci_pipeline_deployments: ci_pipeline_deployments_limit)
+ end
+
+ subject(:quota) { described_class.new(namespace, pipeline, command) }
+
+ shared_context 'limit exceeded' do
+ let(:ci_pipeline_deployments_limit) { 1 }
+ end
+
+ shared_context 'limit not exceeded' do
+ let(:ci_pipeline_deployments_limit) { 2 }
+ end
+
+ describe '#enabled?' do
+ context 'when limit is enabled in plan' do
+ let(:ci_pipeline_deployments_limit) { 10 }
+
+ it 'is enabled' do
+ expect(quota).to be_enabled
+ end
+ end
+
+ context 'when limit is not enabled' do
+ let(:ci_pipeline_deployments_limit) { 0 }
+
+ it 'is not enabled' do
+ expect(quota).not_to be_enabled
+ end
+ end
+
+ context 'when limit does not exist' do
+ before do
+ allow(namespace).to receive(:actual_plan) { create(:default_plan) }
+ end
+
+ it 'is enabled by default' do
+ expect(quota).to be_enabled
+ end
+ end
+ end
+
+ describe '#exceeded?' do
+ context 'when limit is exceeded' do
+ include_context 'limit exceeded'
+
+ it 'is exceeded' do
+ expect(quota).to be_exceeded
+ end
+ end
+
+ context 'when limit is not exceeded' do
+ include_context 'limit not exceeded'
+
+ it 'is not exceeded' do
+ expect(quota).not_to be_exceeded
+ end
+ end
+ end
+
+ describe '#message' do
+ context 'when limit is exceeded' do
+ include_context 'limit exceeded'
+
+ it 'returns info about pipeline deployment limit exceeded' do
+ expect(quota.message)
+ .to eq "Pipeline has too many deployments! Requested 2, but the limit is 1."
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 0b961336f3f..bc10e94c81d 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -71,6 +71,33 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
end
end
+ context 'with job:rules:[variables:]' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true }],
+ rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] }
+ end
+
+ it do
+ is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }])
+ end
+
+ context 'when FF ci_rules_variables is disabled' do
+ before do
+ stub_feature_flags(ci_rules_variables: false)
+ end
+
+ it do
+ is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true }])
+ end
+ end
+ end
+
context 'with cache:key' do
let(:attributes) do
{
@@ -165,6 +192,45 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.to include(options: {}) }
end
+
+ context 'with allow_failure' do
+ let(:options) do
+ { allow_failure_criteria: { exit_codes: [42] } }
+ end
+
+ let(:rules) do
+ [{ if: '$VAR == null', when: 'always' }]
+ end
+
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ options: options,
+ rules: rules
+ }
+ end
+
+ context 'when rules does not override allow_failure' do
+ it { is_expected.to match a_hash_including(options: options) }
+ end
+
+ context 'when rules set allow_failure to true' do
+ let(:rules) do
+ [{ if: '$VAR == null', when: 'always', allow_failure: true }]
+ end
+
+ it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
+ end
+
+ context 'when rules set allow_failure to false' do
+ let(:rules) do
+ [{ if: '$VAR == null', when: 'always', allow_failure: false }]
+ end
+
+ it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
+ end
+ end
end
describe '#bridge?' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
index e62bf042fba..664aaaedf7b 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
@@ -85,16 +85,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do
end
it_behaves_like 'returning a correct environment'
-
- context 'but the environment auto_stop_in on create flag is disabled' do
- let(:expected_auto_stop_in) { nil }
-
- before do
- stub_feature_flags(environment_auto_stop_start_on_create: false)
- end
-
- it_behaves_like 'returning a correct environment'
- end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
new file mode 100644
index 00000000000..1790388da03
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:stages_attributes) do
+ [
+ {
+ name: 'build',
+ index: 0,
+ builds: [
+ { name: 'init', scheduling_type: :stage },
+ { name: 'build', scheduling_type: :stage }
+ ]
+ },
+ {
+ name: 'test',
+ index: 1,
+ builds: [
+ { name: 'rspec', scheduling_type: :stage },
+ { name: 'staging', scheduling_type: :stage, environment: 'staging' },
+ { name: 'deploy', scheduling_type: :stage, environment: 'production' }
+ ]
+ }
+ ]
+ end
+
+ subject(:seed) do
+ described_class.new(pipeline, stages_attributes)
+ end
+
+ describe '#stages' do
+ it 'returns the stage resources' do
+ stages = seed.stages
+
+ expect(stages).to all(be_a(Ci::Stage))
+ expect(stages.map(&:name)).to contain_exactly('build', 'test')
+ end
+ end
+
+ describe '#size' do
+ it 'returns the number of jobs' do
+ expect(seed.size).to eq(5)
+ end
+ end
+
+ describe '#errors' do
+ context 'when attributes are valid' do
+ it 'returns nil' do
+ expect(seed.errors).to be_nil
+ end
+ end
+
+ context 'when attributes are not valid' do
+ it 'returns the errors' do
+ stages_attributes[0][:builds] << {
+ name: 'invalid_job',
+ scheduling_type: :dag,
+ needs_attributes: [{ name: 'non-existent', artifacts: true }]
+ }
+
+ expect(seed.errors).to contain_exactly("invalid_job: needs 'non-existent'")
+ end
+ end
+ end
+
+ describe '#deployments_count' do
+ it 'counts the jobs having an environment associated' do
+ expect(seed.deployments_count).to eq(2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
index 650ae41320b..ade0e36cf1e 100644
--- a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
- let(:comparer) { described_class.new(base_reports, head_reports) }
- let(:base_reports) { Gitlab::Ci::Reports::AccessibilityReports.new }
- let(:head_reports) { Gitlab::Ci::Reports::AccessibilityReports.new }
+ let(:comparer) { described_class.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
let(:url) { "https://gitlab.com" }
let(:single_error) do
[
@@ -38,233 +38,254 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
end
describe '#status' do
- subject { comparer.status }
+ subject(:status) { comparer.status }
context 'when head report has an error' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns status failed' do
- expect(subject).to eq(described_class::STATUS_FAILED)
+ expect(status).to eq(described_class::STATUS_FAILED)
end
end
context 'when head reports does not have errors' do
before do
- head_reports.add_url(url, [])
+ head_report.add_url(url, [])
end
it 'returns status success' do
- expect(subject).to eq(described_class::STATUS_SUCCESS)
+ expect(status).to eq(described_class::STATUS_SUCCESS)
end
end
end
describe '#errors_count' do
- subject { comparer.errors_count }
+ subject(:errors_count) { comparer.errors_count }
context 'when head report has an error' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns the number of new errors' do
- expect(subject).to eq(1)
+ expect(errors_count).to eq(1)
end
end
context 'when head reports does not have an error' do
before do
- head_reports.add_url(url, [])
+ head_report.add_url(url, [])
end
it 'returns the number new errors' do
- expect(subject).to eq(0)
+ expect(errors_count).to eq(0)
end
end
end
describe '#resolved_count' do
- subject { comparer.resolved_count }
+ subject(:resolved_count) { comparer.resolved_count }
context 'when base reports has an error and head has a different error' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, different_error)
end
it 'returns the resolved count' do
- expect(subject).to eq(1)
+ expect(resolved_count).to eq(1)
end
end
context 'when base reports has errors head has no errors' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, [])
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, [])
end
it 'returns the resolved count' do
- expect(subject).to eq(1)
+ expect(resolved_count).to eq(1)
end
end
context 'when base reports has errors and head has the same error' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, single_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns zero' do
- expect(subject).to eq(0)
+ expect(resolved_count).to eq(0)
end
end
context 'when base reports does not have errors and head has errors' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns the number of resolved errors' do
- expect(subject).to eq(0)
+ expect(resolved_count).to eq(0)
end
end
end
describe '#total_count' do
- subject { comparer.total_count }
+ subject(:total_count) { comparer.total_count }
context 'when base reports has an error' do
before do
- base_reports.add_url(url, single_error)
+ base_report.add_url(url, single_error)
end
- it 'returns the error count' do
- expect(subject).to eq(1)
+ it 'returns zero' do
+ expect(total_count).to be_zero
end
end
context 'when head report has an error' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
- it 'returns the error count' do
- expect(subject).to eq(1)
+ it 'returns the total count' do
+ expect(total_count).to eq(1)
end
end
context 'when base report has errors and head report has errors' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, different_error)
+ end
+
+ it 'returns the total count' do
+ expect(total_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head report has the same error' do
+ before do
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, single_error + different_error)
end
- it 'returns the error count' do
- expect(subject).to eq(2)
+ it 'returns the total count' do
+ expect(total_count).to eq(2)
end
end
end
describe '#existing_errors' do
- subject { comparer.existing_errors }
+ subject(:existing_errors) { comparer.existing_errors }
context 'when base report has errors and head has a different error' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, different_error)
end
- it 'returns the existing errors' do
- expect(subject.size).to eq(1)
- expect(subject.first["code"]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
+ it 'returns an empty array' do
+ expect(existing_errors).to be_empty
end
end
context 'when base report does not have errors and head has errors' do
before do
- base_reports.add_url(url, [])
- head_reports.add_url(url, single_error)
+ base_report.add_url(url, [])
+ head_report.add_url(url, single_error)
end
it 'returns an empty array' do
- expect(subject).to be_empty
+ expect(existing_errors).to be_empty
+ end
+ end
+
+ context 'when base report has errors and head report has the same error' do
+ before do
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, single_error + different_error)
+ end
+
+ it 'returns the existing error' do
+ expect(existing_errors).to eq(single_error)
end
end
end
describe '#new_errors' do
- subject { comparer.new_errors }
+ subject(:new_errors) { comparer.new_errors }
context 'when base reports has errors and head has more errors' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, single_error + different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, single_error + different_error)
end
it 'returns new errors between base and head reports' do
- expect(subject.size).to eq(1)
- expect(subject.first["code"]).to eq("WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail")
+ expect(new_errors.size).to eq(1)
+ expect(new_errors.first["code"]).to eq("WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail")
end
end
context 'when base reports has an error and head has no errors' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, [])
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, [])
end
it 'returns an empty array' do
- expect(subject).to be_empty
+ expect(new_errors).to be_empty
end
end
context 'when base reports does not have errors and head has errors' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns the new error' do
- expect(subject.size).to eq(1)
- expect(subject.first["code"]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
+ expect(new_errors.size).to eq(1)
+ expect(new_errors.first["code"]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
end
end
end
describe '#resolved_errors' do
- subject { comparer.resolved_errors }
+ subject(:resolved_errors) { comparer.resolved_errors }
context 'when base report has errors and head has more errors' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, single_error + different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, single_error + different_error)
end
it 'returns an empty array' do
- expect(subject).to be_empty
+ expect(resolved_errors).to be_empty
end
end
context 'when base reports has errors and head has a different error' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, different_error)
end
it 'returns the resolved errors' do
- expect(subject.size).to eq(1)
- expect(subject.first["code"]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
+ expect(resolved_errors.size).to eq(1)
+ expect(resolved_errors.first["code"]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
end
end
context 'when base reports does not have errors and head has errors' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns an empty array' do
- expect(subject).to be_empty
+ expect(resolved_errors).to be_empty
end
end
end
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
new file mode 100644
index 00000000000..7053d54381b
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
@@ -0,0 +1,308 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
+ let(:comparer) { described_class.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:degradation_1) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ let(:degradation_2) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 14,
+ "line": 10
+ },
+ "end": {
+ "column": 39,
+ "line": 10
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ describe '#status' do
+ subject(:report_status) { comparer.status }
+
+ context 'when head report has an error' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns status failed' do
+ expect(report_status).to eq(described_class::STATUS_FAILED)
+ end
+ end
+
+ context 'when head report does not have errors' do
+ it 'returns status success' do
+ expect(report_status).to eq(described_class::STATUS_SUCCESS)
+ end
+ end
+ end
+
+ describe '#errors_count' do
+ subject(:errors_count) { comparer.errors_count }
+
+ context 'when head report has an error' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns the number of new errors' do
+ expect(errors_count).to eq(1)
+ end
+ end
+
+ context 'when head report does not have an error' do
+ it 'returns zero' do
+ expect(errors_count).to be_zero
+ end
+ end
+ end
+
+ describe '#resolved_count' do
+ subject(:resolved_count) { comparer.resolved_count }
+
+ context 'when base report has an error and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'counts the base report error as resolved' do
+ expect(resolved_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors head has no errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ end
+
+ it 'counts the base report errors as resolved' do
+ expect(resolved_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head has the same error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns zero' do
+ expect(resolved_count).to eq(0)
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns zero' do
+ expect(resolved_count).to be_zero
+ end
+ end
+ end
+
+ describe '#total_count' do
+ subject(:total_count) { comparer.total_count }
+
+ context 'when base report has an error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ end
+
+ it 'returns zero' do
+ expect(total_count).to be_zero
+ end
+ end
+
+ context 'when head report has an error' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'includes the head report error in the count' do
+ expect(total_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head report has errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes errors in the count' do
+ expect(total_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head report has the same error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes errors in the count' do
+ expect(total_count).to eq(2)
+ end
+ end
+ end
+
+ describe '#existing_errors' do
+ subject(:existing_errors) { comparer.existing_errors }
+
+ context 'when base report has errors and head has the same error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes the base report errors' do
+ expect(existing_errors).to contain_exactly(degradation_1)
+ end
+ end
+
+ context 'when base report has errors and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'returns an empty array' do
+ expect(existing_errors).to be_empty
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns an empty array' do
+ expect(existing_errors).to be_empty
+ end
+ end
+ end
+
+ describe '#new_errors' do
+ subject(:new_errors) { comparer.new_errors }
+
+ context 'when base report has errors and head has more errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes errors not found in the base report' do
+ expect(new_errors).to eq([degradation_2])
+ end
+ end
+
+ context 'when base report has an error and head has no errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ end
+
+ it 'returns an empty array' do
+ expect(new_errors).to be_empty
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns the head report error' do
+ expect(new_errors).to eq([degradation_1])
+ end
+ end
+ end
+
+ describe '#resolved_errors' do
+ subject(:resolved_errors) { comparer.resolved_errors }
+
+ context 'when base report errors are still found in the head report' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'returns an empty array' do
+ expect(resolved_errors).to be_empty
+ end
+ end
+
+ context 'when base report has errors and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'returns the base report error' do
+ expect(resolved_errors).to eq([degradation_1])
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns an empty array' do
+ expect(resolved_errors).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
new file mode 100644
index 00000000000..44e67259369
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
+ let(:codequality_report) { described_class.new }
+ let(:degradation_1) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ let(:degradation_2) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 14,
+ "line": 10
+ },
+ "end": {
+ "column": 39,
+ "line": 10
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ it { expect(codequality_report.degradations).to eq({}) }
+
+ describe '#add_degradation' do
+ context 'when there is a degradation' do
+ before do
+ codequality_report.add_degradation(degradation_1)
+ end
+
+ it 'adds degradation to codequality report' do
+ expect(codequality_report.degradations.keys).to eq([degradation_1[:fingerprint]])
+ expect(codequality_report.degradations.values.size).to eq(1)
+ end
+ end
+
+ context 'when a required property is missing in the degradation' do
+ let(:invalid_degradation) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "fingerprint": "ab5f8b935886b942d621399aefkaehfiaehf",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ it 'sets location as an error' do
+ codequality_report.add_degradation(invalid_degradation)
+
+ expect(codequality_report.error_message).to eq("Invalid degradation format: The property '#/' did not contain a required property of 'location'")
+ end
+ end
+ end
+
+ describe '#set_error_message' do
+ context 'when there is an error' do
+ it 'sets errors' do
+ codequality_report.set_error_message("error")
+
+ expect(codequality_report.error_message).to eq("error")
+ end
+ end
+ end
+
+ describe '#degradations_count' do
+ subject(:degradations_count) { codequality_report.degradations_count }
+
+ context 'when there are many degradations' do
+ before do
+ codequality_report.add_degradation(degradation_1)
+ codequality_report.add_degradation(degradation_2)
+ end
+
+ it 'returns the number of degradations' do
+ expect(degradations_count).to eq(2)
+ end
+ end
+ end
+
+ describe '#all_degradations' do
+ subject(:all_degradations) { codequality_report.all_degradations }
+
+ context 'when there are many degradations' do
+ before do
+ codequality_report.add_degradation(degradation_1)
+ codequality_report.add_degradation(degradation_2)
+ end
+
+ it 'returns all degradations' do
+ expect(all_degradations).to contain_exactly(degradation_1, degradation_2)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/reports_comparer_spec.rb
new file mode 100644
index 00000000000..1e5e4766583
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/reports_comparer_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::ReportsComparer do
+ let(:comparer) { described_class.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+
+ describe '#initialize' do
+ context 'sets getter for the report comparer' do
+ it 'return base report' do
+ expect(comparer.base_report).to be_an_instance_of(Gitlab::Ci::Reports::CodequalityReports)
+ end
+
+ it 'return head report' do
+ expect(comparer.head_report).to be_an_instance_of(Gitlab::Ci::Reports::CodequalityReports)
+ end
+ end
+ end
+
+ describe '#status' do
+ subject(:status) { comparer.status }
+
+ it 'returns not implemented error' do
+ expect { status }.to raise_error(NotImplementedError)
+ end
+
+ context 'when success? is true' do
+ before do
+ allow(comparer).to receive(:success?).and_return(true)
+ end
+
+ it 'returns status success' do
+ expect(status).to eq('success')
+ end
+ end
+
+ context 'when success? is false' do
+ before do
+ allow(comparer).to receive(:success?).and_return(false)
+ end
+
+ it 'returns status failed' do
+ expect(status).to eq('failed')
+ end
+ end
+ end
+
+ describe '#success?' do
+ subject(:success?) { comparer.success? }
+
+ it 'returns not implemented error' do
+ expect { success? }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#existing_errors' do
+ subject(:existing_errors) { comparer.existing_errors }
+
+ it 'returns not implemented error' do
+ expect { existing_errors }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#resolved_errors' do
+ subject(:resolved_errors) { comparer.resolved_errors }
+
+ it 'returns not implemented error' do
+ expect { resolved_errors }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#errors_count' do
+ subject(:errors_count) { comparer.errors_count }
+
+ it 'returns not implemented error' do
+ expect { errors_count }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#resolved_count' do
+ subject(:resolved_count) { comparer.resolved_count }
+
+ it 'returns not implemented error' do
+ expect { resolved_count }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#total_count' do
+ subject(:total_count) { comparer.total_count }
+
+ it 'returns not implemented error' do
+ expect { total_count }.to raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/npm_spec.rb b/spec/lib/gitlab/ci/templates/npm_spec.rb
new file mode 100644
index 00000000000..1f8e32ce019
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/npm_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'npm.latest.gitlab-ci.yml' do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('npm.latest') }
+
+ describe 'the created pipeline' do
+ let_it_be(:user) { create(:admin) }
+
+ let(:repo_files) { { 'package.json' => '{}', 'README.md' => '' } }
+ let(:modified_files) { %w[package.json] }
+ let(:project) { create(:project, :custom_repo, files: repo_files) }
+ let(:pipeline_branch) { project.default_branch }
+ let(:pipeline_tag) { 'v1.2.1' }
+ let(:pipeline_ref) { pipeline_branch }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref ) }
+ let(:pipeline) { service.execute!(:push) }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ def create_branch(name:)
+ ::Branches::CreateService.new(project, user).execute(name, project.default_branch)
+ end
+
+ def create_tag(name:)
+ ::Tags::CreateService.new(project, user).execute(name, project.default_branch, nil)
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+
+ create_branch(name: pipeline_branch)
+ create_tag(name: pipeline_tag)
+
+ allow_any_instance_of(Ci::Pipeline).to receive(:modified_paths).and_return(modified_files)
+ end
+
+ shared_examples 'publish job created' do
+ it 'creates a pipeline with a single job: publish' do
+ expect(build_names).to eq(%w[publish])
+ end
+ end
+
+ shared_examples 'no pipeline created' do
+ it 'does not create a pipeline because the only job (publish) is not created' do
+ expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError, 'No stages / jobs for this pipeline.')
+ end
+ end
+
+ context 'on default branch' do
+ context 'when package.json has been changed' do
+ it_behaves_like 'publish job created'
+ end
+
+ context 'when package.json does not exist or has not been changed' do
+ let(:modified_files) { %w[README.md] }
+
+ it_behaves_like 'no pipeline created'
+ end
+ end
+
+ %w[v1.0.0 v2.1.0-alpha].each do |valid_version|
+ context "when the branch name is #{valid_version}" do
+ let(:pipeline_branch) { valid_version }
+
+ it_behaves_like 'publish job created'
+ end
+
+ context "when the tag name is #{valid_version}" do
+ let(:pipeline_tag) { valid_version }
+ let(:pipeline_ref) { pipeline_tag }
+
+ it_behaves_like 'publish job created'
+ end
+ end
+
+ %w[patch-1 my-feature-branch v1 v1.0 2.1.0].each do |invalid_version|
+ context "when the branch name is #{invalid_version}" do
+ let(:pipeline_branch) { invalid_version }
+
+ it_behaves_like 'no pipeline created'
+ end
+
+ context "when the tag name is #{invalid_version}" do
+ let(:pipeline_tag) { invalid_version }
+ let(:pipeline_ref) { pipeline_tag }
+
+ it_behaves_like 'no pipeline created'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/trace/checksum_spec.rb b/spec/lib/gitlab/ci/trace/checksum_spec.rb
index 794794c3f69..a343d74f755 100644
--- a/spec/lib/gitlab/ci/trace/checksum_spec.rb
+++ b/spec/lib/gitlab/ci/trace/checksum_spec.rb
@@ -8,8 +8,12 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
subject { described_class.new(build) }
context 'when build pending state exists' do
+ let(:trace_details) do
+ { trace_checksum: 'crc32:d4777540', trace_bytesize: 262161 }
+ end
+
before do
- create(:ci_build_pending_state, build: build, trace_checksum: 'crc32:d4777540')
+ create(:ci_build_pending_state, build: build, **trace_details)
end
context 'when matching persisted trace chunks exist' do
@@ -22,6 +26,7 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
it 'calculates combined trace chunks CRC32 correctly' do
expect(subject.chunks_crc32).to eq 3564598592
expect(subject).to be_valid
+ expect(subject).not_to be_corrupted
end
end
@@ -32,8 +37,9 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
create_chunk(index: 2, data: 'ccccccccccccccccc')
end
- it 'makes trace checksum invalid' do
+ it 'makes trace checksum invalid but not corrupted' do
expect(subject).not_to be_valid
+ expect(subject).not_to be_corrupted
end
end
@@ -43,8 +49,9 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
create_chunk(index: 2, data: 'ccccccccccccccccc')
end
- it 'makes trace checksum invalid' do
+ it 'makes trace checksum invalid and corrupted' do
expect(subject).not_to be_valid
+ expect(subject).to be_corrupted
end
end
@@ -55,8 +62,9 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
create_chunk(index: 2, data: 'ccccccccccccccccc')
end
- it 'makes trace checksum invalid' do
+ it 'makes trace checksum invalid but not corrupted' do
expect(subject).not_to be_valid
+ expect(subject).not_to be_corrupted
end
end
@@ -99,6 +107,14 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
it 'returns nil' do
expect(subject.last_chunk).to be_nil
end
+
+ it 'is not a valid trace' do
+ expect(subject).not_to be_valid
+ end
+
+ it 'is not a corrupted trace' do
+ expect(subject).not_to be_corrupted
+ end
end
context 'when there are multiple chunks' do
@@ -110,6 +126,26 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
it 'returns chunk with the highest index' do
expect(subject.last_chunk.chunk_index).to eq 1
end
+
+ it 'is not a valid trace' do
+ expect(subject).not_to be_valid
+ end
+
+ it 'is not a corrupted trace' do
+ expect(subject).not_to be_corrupted
+ end
+ end
+ end
+
+ describe '#trace_size' do
+ before do
+ create_chunk(index: 0, data: 'a' * 128.kilobytes)
+ create_chunk(index: 1, data: 'b' * 128.kilobytes)
+ create_chunk(index: 2, data: 'abcdefg-ü')
+ end
+
+ it 'returns total trace size in bytes' do
+ expect(subject.trace_size).to eq 262154
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index fb6395e888a..5ad1b3dd241 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -231,6 +231,23 @@ module Gitlab
expect(subject[:allow_failure]).to be true
end
end
+
+ context 'when allow_failure has exit_codes' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec',
+ when: 'manual',
+ allow_failure: { exit_codes: 1 } })
+ end
+
+ it 'is not allowed to fail' do
+ expect(subject[:allow_failure]).to be false
+ end
+
+ it 'saves allow_failure_criteria into options' do
+ expect(subject[:options]).to match(
+ a_hash_including(allow_failure_criteria: { exit_codes: [1] }))
+ end
+ end
end
context 'when job is not a manual action' do
@@ -254,6 +271,22 @@ module Gitlab
expect(subject[:allow_failure]).to be false
end
end
+
+ context 'when allow_failure is dynamically specified' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec',
+ allow_failure: { exit_codes: 1 } })
+ end
+
+ it 'is not allowed to fail' do
+ expect(subject[:allow_failure]).to be false
+ end
+
+ it 'saves allow_failure_criteria into options' do
+ expect(subject[:options]).to match(
+ a_hash_including(allow_failure_criteria: { exit_codes: [1] }))
+ end
+ end
end
end
@@ -2111,6 +2144,71 @@ module Gitlab
end
end
+ describe 'cross pipeline needs' do
+ context 'when configuration is valid' do
+ let(:config) do
+ <<~YAML
+ rspec:
+ stage: test
+ script: rspec
+ needs:
+ - pipeline: $THE_PIPELINE_ID
+ job: dependency-job
+ YAML
+ end
+
+ it 'returns a valid configuration and sets artifacts: true by default' do
+ expect(subject).to be_valid
+
+ rspec = subject.build_attributes(:rspec)
+ expect(rspec.dig(:options, :cross_dependencies)).to eq(
+ [{ pipeline: '$THE_PIPELINE_ID', job: 'dependency-job', artifacts: true }]
+ )
+ end
+
+ context 'when pipeline ID is hard-coded' do
+ let(:config) do
+ <<~YAML
+ rspec:
+ stage: test
+ script: rspec
+ needs:
+ - pipeline: "123"
+ job: dependency-job
+ YAML
+ end
+
+ it 'returns a valid configuration and sets artifacts: true by default' do
+ expect(subject).to be_valid
+
+ rspec = subject.build_attributes(:rspec)
+ expect(rspec.dig(:options, :cross_dependencies)).to eq(
+ [{ pipeline: '123', job: 'dependency-job', artifacts: true }]
+ )
+ end
+ end
+ end
+
+ context 'when configuration is not valid' do
+ let(:config) do
+ <<~YAML
+ rspec:
+ stage: test
+ script: rspec
+ needs:
+ - pipeline: $THE_PIPELINE_ID
+ job: dependency-job
+ something: else
+ YAML
+ end
+
+ it 'returns an error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors).to include(/:need config contains unknown keys: something/)
+ end
+ end
+ end
+
describe "Hidden jobs" do
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config).execute }
@@ -2429,7 +2527,13 @@ module Gitlab
context 'returns errors if job allow_failure parameter is not an boolean' do
let(:config) { YAML.dump({ rspec: { script: "test", allow_failure: "string" } }) }
- it_behaves_like 'returns errors', 'jobs:rspec allow failure should be a boolean value'
+ it_behaves_like 'returns errors', 'jobs:rspec allow failure should be a hash or a boolean value'
+ end
+
+ context 'returns errors if job exit_code parameter from allow_failure is not an integer' do
+ let(:config) { YAML.dump({ rspec: { script: "test", allow_failure: { exit_codes: 'string' } } }) }
+
+ it_behaves_like 'returns errors', 'jobs:rspec:allow_failure exit codes should be an array of integers or an integer'
end
context 'returns errors if job stage is not a string' do
diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
index 05d744d95e2..a99bdcc9a0f 100644
--- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb
+++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
@@ -15,10 +15,10 @@ RSpec.describe Gitlab::Cleanup::ProjectUploads do
describe '#run!' do
shared_examples_for 'moves the file' do
shared_examples_for 'a real run' do
- let(:args) { [dry_run: false] }
+ let(:args) { { dry_run: false } }
it 'moves the file to its proper location' do
- subject.run!(*args)
+ subject.run!(**args)
expect(File.exist?(path)).to be_falsey
expect(File.exist?(new_path)).to be_truthy
@@ -28,13 +28,13 @@ RSpec.describe Gitlab::Cleanup::ProjectUploads do
expect(logger).to receive(:info).with("Looking for orphaned project uploads to clean up...")
expect(logger).to receive(:info).with("Did #{action}")
- subject.run!(*args)
+ subject.run!(**args)
end
end
shared_examples_for 'a dry run' do
it 'does not move the file' do
- subject.run!(*args)
+ subject.run!(**args)
expect(File.exist?(path)).to be_truthy
expect(File.exist?(new_path)).to be_falsey
@@ -44,30 +44,30 @@ RSpec.describe Gitlab::Cleanup::ProjectUploads do
expect(logger).to receive(:info).with("Looking for orphaned project uploads to clean up. Dry run...")
expect(logger).to receive(:info).with("Can #{action}")
- subject.run!(*args)
+ subject.run!(**args)
end
end
context 'when dry_run is false' do
- let(:args) { [dry_run: false] }
+ let(:args) { { dry_run: false } }
it_behaves_like 'a real run'
end
context 'when dry_run is nil' do
- let(:args) { [dry_run: nil] }
+ let(:args) { { dry_run: nil } }
it_behaves_like 'a real run'
end
context 'when dry_run is true' do
- let(:args) { [dry_run: true] }
+ let(:args) { { dry_run: true } }
it_behaves_like 'a dry run'
end
context 'with dry_run not specified' do
- let(:args) { [] }
+ let(:args) { {} }
it_behaves_like 'a dry run'
end
diff --git a/spec/lib/gitlab/config/entry/configurable_spec.rb b/spec/lib/gitlab/config/entry/configurable_spec.rb
index c72efa66024..0153cfbf091 100644
--- a/spec/lib/gitlab/config/entry/configurable_spec.rb
+++ b/spec/lib/gitlab/config/entry/configurable_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::Config::Entry::Configurable do
describe 'validations' do
context 'when entry is a hash' do
- let(:instance) { entry.new(key: 'value') }
+ let(:instance) { entry.new({ key: 'value' }) }
it 'correctly validates an instance' do
expect(instance).to be_valid
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 2c5988f06b2..553f33a66c4 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -40,6 +40,9 @@ RSpec.describe 'value stream analytics events', :aggregate_failures do
before do
create_commit_referencing_issue(context)
+
+ # Adding extra duration because the new VSA backend filters out 0 durations between these columns
+ context.metrics.update!(first_mentioned_in_commit_at: context.metrics.first_associated_with_milestone_at + 1.day)
end
it 'has correct attributes' do
diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index 719d4a69985..21503dc1501 100644
--- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do
project.add_maintainer(user)
end
- let(:stage_summary) { described_class.new(project, options).data }
+ let(:stage_summary) { described_class.new(project, **options).data }
describe "#new_issues" do
subject { stage_summary.first }
@@ -121,7 +121,7 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do
end
it 'does not include commit stats' do
- data = described_class.new(project, options).data
+ data = described_class.new(project, **options).data
expect(includes_commits?(data)).to be_falsy
end
diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
deleted file mode 100644
index 9ebdacb16de..00000000000
--- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::UsageData do
- describe '#to_json' do
- before do
- # Since git commits only have second precision, round up to the
- # nearest second to ensure we have accurate median and standard
- # deviation calculations.
- current_time = Time.at(Time.now.to_i)
-
- Timecop.freeze(current_time) do
- user = create(:user, :admin)
- projects = create_list(:project, 2, :repository)
-
- projects.each_with_index do |project, time|
- issue = create(:issue, project: project, created_at: (time + 1).hour.ago)
-
- allow_next_instance_of(Gitlab::ReferenceExtractor) do |instance|
- allow(instance).to receive(:issues).and_return([issue])
- end
-
- milestone = create(:milestone, project: project)
- mr = create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}")
- pipeline = create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr)
-
- create_cycle(user, project, issue, mr, milestone, pipeline)
- deploy_master(user, project, environment: 'staging')
- deploy_master(user, project)
- end
- end
- end
-
- context 'a valid usage data result' do
- let(:expect_values_per_stage) do
- {
- issue: {
- average: 5400,
- sd: 2545,
- missing: 0
- },
- plan: {
- average: 1,
- sd: 0,
- missing: 0
- },
- code: {
- average: nil,
- sd: 0,
- missing: 2
- },
- test: {
- average: nil,
- sd: 0,
- missing: 2
- },
- review: {
- average: 0,
- sd: 0,
- missing: 0
- },
- staging: {
- average: 0,
- sd: 0,
- missing: 0
- },
- production: {
- average: 5400,
- sd: 2545,
- missing: 0
- }
- }
- end
-
- it 'returns the aggregated usage data of every selected project', :sidekiq_might_not_need_inline do
- result = subject.to_json
-
- expect(result).to have_key(:avg_cycle_analytics)
-
- CycleAnalytics::LevelBase::STAGES.each do |stage|
- expect(result[:avg_cycle_analytics]).to have_key(stage)
-
- stage_values = result[:avg_cycle_analytics][stage]
- expected_values = expect_values_per_stage[stage]
-
- expected_values.each_pair do |op, value|
- expect(stage_values).to have_key(op)
- expect(stage_values[op]).to eq(value)
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/base_linter_spec.rb b/spec/lib/gitlab/danger/base_linter_spec.rb
new file mode 100644
index 00000000000..bd0ceb5a125
--- /dev/null
+++ b/spec/lib/gitlab/danger/base_linter_spec.rb
@@ -0,0 +1,154 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_relative 'danger_spec_helper'
+
+require 'gitlab/danger/base_linter'
+
+RSpec.describe Gitlab::Danger::BaseLinter do
+ let(:commit_class) do
+ Struct.new(:message, :sha, :diff_parent)
+ end
+
+ let(:commit_message) { 'A commit message' }
+ let(:commit) { commit_class.new(commit_message, anything, anything) }
+
+ subject(:commit_linter) { described_class.new(commit) }
+
+ describe '#failed?' do
+ context 'with no failures' do
+ it { expect(commit_linter).not_to be_failed }
+ end
+
+ context 'with failures' do
+ before do
+ commit_linter.add_problem(:subject_too_long, described_class.subject_description)
+ end
+
+ it { expect(commit_linter).to be_failed }
+ end
+ end
+
+ describe '#add_problem' do
+ it 'stores messages in #failures' do
+ commit_linter.add_problem(:subject_too_long, '%s')
+
+ expect(commit_linter.problems).to eq({ subject_too_long: described_class.problems_mapping[:subject_too_long] })
+ end
+ end
+
+ shared_examples 'a valid commit' do
+ it 'does not have any problem' do
+ commit_linter.lint_subject
+
+ expect(commit_linter.problems).to be_empty
+ end
+ end
+
+ describe '#lint_subject' do
+ context 'when subject valid' do
+ it_behaves_like 'a valid commit'
+ end
+
+ context 'when subject is too short' do
+ let(:commit_message) { 'A B' }
+
+ it 'adds a problem' do
+ expect(commit_linter).to receive(:add_problem).with(:subject_too_short, described_class.subject_description)
+
+ commit_linter.lint_subject
+ end
+ end
+
+ context 'when subject is too long' do
+ let(:commit_message) { 'A B ' + 'C' * described_class::MAX_LINE_LENGTH }
+
+ it 'adds a problem' do
+ expect(commit_linter).to receive(:add_problem).with(:subject_too_long, described_class.subject_description)
+
+ commit_linter.lint_subject
+ end
+ end
+
+ context 'when subject is a WIP' do
+ let(:final_message) { 'A B C' }
+ # commit message with prefix will be over max length. commit message without prefix will be of maximum size
+ let(:commit_message) { described_class::WIP_PREFIX + final_message + 'D' * (described_class::MAX_LINE_LENGTH - final_message.size) }
+
+ it 'does not have any problems' do
+ commit_linter.lint_subject
+
+ expect(commit_linter.problems).to be_empty
+ end
+ end
+
+ context 'when subject is too short and too long' do
+ let(:commit_message) { 'A ' + 'B' * described_class::MAX_LINE_LENGTH }
+
+ it 'adds a problem' do
+ expect(commit_linter).to receive(:add_problem).with(:subject_too_short, described_class.subject_description)
+ expect(commit_linter).to receive(:add_problem).with(:subject_too_long, described_class.subject_description)
+
+ commit_linter.lint_subject
+ end
+ end
+
+ context 'when subject starts with lowercase' do
+ let(:commit_message) { 'a B C' }
+
+ it 'adds a problem' do
+ expect(commit_linter).to receive(:add_problem).with(:subject_starts_with_lowercase, described_class.subject_description)
+
+ commit_linter.lint_subject
+ end
+ end
+
+ [
+ '[ci skip] A commit message',
+ '[Ci skip] A commit message',
+ '[API] A commit message',
+ 'api: A commit message',
+ 'API: A commit message',
+ 'API: a commit message',
+ 'API: a commit message'
+ ].each do |message|
+ context "when subject is '#{message}'" do
+ let(:commit_message) { message }
+
+ it 'does not add a problem' do
+ expect(commit_linter).not_to receive(:add_problem)
+
+ commit_linter.lint_subject
+ end
+ end
+ end
+
+ [
+ '[ci skip]A commit message',
+ '[Ci skip] A commit message',
+ '[ci skip] a commit message',
+ 'api: a commit message',
+ '! A commit message'
+ ].each do |message|
+ context "when subject is '#{message}'" do
+ let(:commit_message) { message }
+
+ it 'adds a problem' do
+ expect(commit_linter).to receive(:add_problem).with(:subject_starts_with_lowercase, described_class.subject_description)
+
+ commit_linter.lint_subject
+ end
+ end
+ end
+
+ context 'when subject ends with a period' do
+ let(:commit_message) { 'A B C.' }
+
+ it 'adds a problem' do
+ expect(commit_linter).to receive(:add_problem).with(:subject_ends_with_a_period, described_class.subject_description)
+
+ commit_linter.lint_subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/commit_linter_spec.rb b/spec/lib/gitlab/danger/commit_linter_spec.rb
index ebfeedba700..d3d86037a53 100644
--- a/spec/lib/gitlab/danger/commit_linter_spec.rb
+++ b/spec/lib/gitlab/danger/commit_linter_spec.rb
@@ -98,28 +98,6 @@ RSpec.describe Gitlab::Danger::CommitLinter do
end
end
- describe '#failed?' do
- context 'with no failures' do
- it { expect(commit_linter).not_to be_failed }
- end
-
- context 'with failures' do
- before do
- commit_linter.add_problem(:details_line_too_long)
- end
-
- it { expect(commit_linter).to be_failed }
- end
- end
-
- describe '#add_problem' do
- it 'stores messages in #failures' do
- commit_linter.add_problem(:details_line_too_long)
-
- expect(commit_linter.problems).to eq({ details_line_too_long: described_class::PROBLEMS[:details_line_too_long] })
- end
- end
-
shared_examples 'a valid commit' do
it 'does not have any problem' do
commit_linter.lint
@@ -129,113 +107,6 @@ RSpec.describe Gitlab::Danger::CommitLinter do
end
describe '#lint' do
- describe 'subject' do
- context 'when subject valid' do
- it_behaves_like 'a valid commit'
- end
-
- context 'when subject is too short' do
- let(:commit_message) { 'A B' }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_too_short, described_class::DEFAULT_SUBJECT_DESCRIPTION)
-
- commit_linter.lint
- end
- end
-
- context 'when subject is too long' do
- let(:commit_message) { 'A B ' + 'C' * described_class::MAX_LINE_LENGTH }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_too_long, described_class::DEFAULT_SUBJECT_DESCRIPTION)
-
- commit_linter.lint
- end
- end
-
- context 'when subject is a WIP' do
- let(:final_message) { 'A B C' }
- # commit message with prefix will be over max length. commit message without prefix will be of maximum size
- let(:commit_message) { described_class::WIP_PREFIX + final_message + 'D' * (described_class::MAX_LINE_LENGTH - final_message.size) }
-
- it 'does not have any problems' do
- commit_linter.lint
-
- expect(commit_linter.problems).to be_empty
- end
- end
-
- context 'when subject is too short and too long' do
- let(:commit_message) { 'A ' + 'B' * described_class::MAX_LINE_LENGTH }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_too_short, described_class::DEFAULT_SUBJECT_DESCRIPTION)
- expect(commit_linter).to receive(:add_problem).with(:subject_too_long, described_class::DEFAULT_SUBJECT_DESCRIPTION)
-
- commit_linter.lint
- end
- end
-
- context 'when subject starts with lowercase' do
- let(:commit_message) { 'a B C' }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_starts_with_lowercase, described_class::DEFAULT_SUBJECT_DESCRIPTION)
-
- commit_linter.lint
- end
- end
-
- [
- '[ci skip] A commit message',
- '[Ci skip] A commit message',
- '[API] A commit message',
- 'api: A commit message',
- 'API: A commit message'
- ].each do |message|
- context "when subject is '#{message}'" do
- let(:commit_message) { message }
-
- it 'does not add a problem' do
- expect(commit_linter).not_to receive(:add_problem)
-
- commit_linter.lint
- end
- end
- end
-
- [
- '[ci skip]A commit message',
- '[Ci skip] A commit message',
- '[ci skip] a commit message',
- 'API: a commit message',
- 'API: a commit message',
- 'api: a commit message',
- '! A commit message'
- ].each do |message|
- context "when subject is '#{message}'" do
- let(:commit_message) { message }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_starts_with_lowercase, described_class::DEFAULT_SUBJECT_DESCRIPTION)
-
- commit_linter.lint
- end
- end
- end
-
- context 'when subject ends with a period' do
- let(:commit_message) { 'A B C.' }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_ends_with_a_period, described_class::DEFAULT_SUBJECT_DESCRIPTION)
-
- commit_linter.lint
- end
- end
- end
-
describe 'separator' do
context 'when separator is missing' do
let(:commit_message) { "A B C\n" }
@@ -300,8 +171,10 @@ RSpec.describe Gitlab::Danger::CommitLinter do
end
end
- context 'when details exceeds the max line length including a URL' do
- let(:commit_message) { "A B C\n\nhttps://gitlab.com" + 'D' * described_class::MAX_LINE_LENGTH }
+ context 'when details exceeds the max line length including URLs' do
+ let(:commit_message) do
+ "A B C\n\nsome message with https://example.com and https://gitlab.com" + 'D' * described_class::MAX_LINE_LENGTH
+ end
it_behaves_like 'a valid commit'
end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index f400641706d..a8f113a8cd1 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -33,6 +33,16 @@ RSpec.describe Gitlab::Danger::Helper do
expect(helper.gitlab_helper).to eq(fake_gitlab)
end
end
+
+ context 'when danger gitlab plugin is not available' do
+ it 'returns nil' do
+ invalid_danger = Class.new do
+ include Gitlab::Danger::Helper
+ end.new
+
+ expect(invalid_danger.gitlab_helper).to be_nil
+ end
+ end
end
describe '#release_automation?' do
@@ -591,4 +601,30 @@ RSpec.describe Gitlab::Danger::Helper do
expect(helper.prepare_labels_for_mr([])).to eq('')
end
end
+
+ describe '#has_ci_changes?' do
+ context 'when .gitlab/ci is changed' do
+ it 'returns true' do
+ expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb .gitlab/ci/test.yml])
+
+ expect(helper.has_ci_changes?).to be_truthy
+ end
+ end
+
+ context 'when .gitlab-ci.yml is changed' do
+ it 'returns true' do
+ expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb .gitlab-ci.yml])
+
+ expect(helper.has_ci_changes?).to be_truthy
+ end
+ end
+
+ context 'when neither .gitlab/ci/ or .gitlab-ci.yml is changed' do
+ it 'returns false' do
+ expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb nested/.gitlab-ci.yml])
+
+ expect(helper.has_ci_changes?).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/danger/merge_request_linter_spec.rb b/spec/lib/gitlab/danger/merge_request_linter_spec.rb
new file mode 100644
index 00000000000..29facc9fdd6
--- /dev/null
+++ b/spec/lib/gitlab/danger/merge_request_linter_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+require_relative 'danger_spec_helper'
+
+require 'gitlab/danger/merge_request_linter'
+
+RSpec.describe Gitlab::Danger::MergeRequestLinter do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:mr_class) do
+ Struct.new(:message, :sha, :diff_parent)
+ end
+
+ let(:mr_title) { 'A B ' + 'C' }
+ let(:merge_request) { mr_class.new(mr_title, anything, anything) }
+
+ describe '#lint_subject' do
+ subject(:mr_linter) { described_class.new(merge_request) }
+
+ shared_examples 'a valid mr title' do
+ it 'does not have any problem' do
+ mr_linter.lint
+
+ expect(mr_linter.problems).to be_empty
+ end
+ end
+
+ context 'when subject valid' do
+ it_behaves_like 'a valid mr title'
+ end
+
+ context 'when it is too long' do
+ let(:mr_title) { 'A B ' + 'C' * described_class::MAX_LINE_LENGTH }
+
+ it 'adds a problem' do
+ expect(mr_linter).to receive(:add_problem).with(:subject_too_long, described_class.subject_description)
+
+ mr_linter.lint
+ end
+ end
+
+ describe 'using magic mr run options' do
+ where(run_option: described_class.mr_run_options_regex.split('|') +
+ described_class.mr_run_options_regex.split('|').map! { |x| "[#{x}]" })
+
+ with_them do
+ let(:mr_title) { run_option + ' A B ' + 'C' * (described_class::MAX_LINE_LENGTH - 5) }
+
+ it_behaves_like 'a valid mr title'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/roulette_spec.rb b/spec/lib/gitlab/danger/roulette_spec.rb
index 1a900dfba22..561e108bf31 100644
--- a/spec/lib/gitlab/danger/roulette_spec.rb
+++ b/spec/lib/gitlab/danger/roulette_spec.rb
@@ -165,6 +165,14 @@ RSpec.describe Gitlab::Danger::Roulette do
end
end
+ context 'when change contains many categories' do
+ let(:categories) { [:frontend, :test, :qa, :engineering_productivity, :ci_template, :backend] }
+
+ it 'has a deterministic sorting order' do
+ expect(spins.map(&:category)).to eq categories.sort
+ end
+ end
+
context 'when change contains QA category' do
let(:categories) { [:qa] }
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 4e0cc8a1fa9..e5dfff33a2a 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -21,9 +21,10 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
let(:data) { described_class.build(pipeline) }
let(:attributes) { data[:object_attributes] }
let(:build_data) { data[:builds].first }
+ let(:runner_data) { build_data[:runner] }
let(:project_data) { data[:project] }
- it 'has correct attributes' do
+ it 'has correct attributes', :aggregate_failures do
expect(attributes).to be_a(Hash)
expect(attributes[:ref]).to eq(pipeline.ref)
expect(attributes[:sha]).to eq(pipeline.sha)
@@ -36,6 +37,7 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
expect(build_data[:id]).to eq(build.id)
expect(build_data[:status]).to eq(build.status)
expect(build_data[:allow_failure]).to eq(build.allow_failure)
+ expect(runner_data).to eq(nil)
expect(project_data).to eq(project.hook_attrs(backward: false))
expect(data[:merge_request]).to be_nil
expect(data[:user]).to eq({
@@ -46,6 +48,18 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
})
end
+ context 'build with runner' do
+ let!(:build) { create(:ci_build, pipeline: pipeline, runner: ci_runner) }
+ let(:ci_runner) { create(:ci_runner) }
+
+ it 'has runner attributes', :aggregate_failures do
+ expect(runner_data[:id]).to eq(ci_runner.id)
+ expect(runner_data[:description]).to eq(ci_runner.description)
+ expect(runner_data[:active]).to eq(ci_runner.active)
+ expect(runner_data[:is_shared]).to eq(ci_runner.instance_type?)
+ end
+ end
+
context 'pipeline without variables' do
it 'has empty variables hash' do
expect(attributes[:variables]).to be_a(Array)
diff --git a/spec/lib/gitlab/database/batch_count_spec.rb b/spec/lib/gitlab/database/batch_count_spec.rb
index a1cc759e011..29688b18e94 100644
--- a/spec/lib/gitlab/database/batch_count_spec.rb
+++ b/spec/lib/gitlab/database/batch_count_spec.rb
@@ -130,6 +130,16 @@ RSpec.describe Gitlab::Database::BatchCount do
expect(described_class.batch_count(model, start: model.minimum(:id), finish: model.maximum(:id))).to eq(5)
end
+ it 'stops counting when finish value is reached' do
+ stub_const('Gitlab::Database::BatchCounter::MIN_REQUIRED_BATCH_SIZE', 0)
+
+ expect(described_class.batch_count(model,
+ start: model.minimum(:id),
+ finish: model.maximum(:id) - 1, # Do not count the last record
+ batch_size: model.count - 2 # Ensure there are multiple batches
+ )).to eq(model.count - 1)
+ end
+
it "defaults the batch size to #{Gitlab::Database::BatchCounter::DEFAULT_BATCH_SIZE}" do
min_id = model.minimum(:id)
relation = instance_double(ActiveRecord::Relation)
@@ -242,6 +252,19 @@ RSpec.describe Gitlab::Database::BatchCount do
expect(described_class.batch_distinct_count(model, column, start: model.minimum(column), finish: model.maximum(column))).to eq(2)
end
+ it 'stops counting when finish value is reached' do
+ # Create a new unique author that should not be counted
+ create(:issue)
+
+ stub_const('Gitlab::Database::BatchCounter::MIN_REQUIRED_BATCH_SIZE', 0)
+
+ expect(described_class.batch_distinct_count(model, column,
+ start: User.minimum(:id),
+ finish: User.maximum(:id) - 1, # Do not count the newly created issue
+ batch_size: model.count - 2 # Ensure there are multiple batches
+ )).to eq(2)
+ end
+
it 'counts with User min and max as start and finish' do
expect(described_class.batch_distinct_count(model, column, start: User.minimum(:id), finish: User.maximum(:id))).to eq(2)
end
diff --git a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
index 48132d68031..3e8563376ce 100644
--- a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
@@ -189,7 +189,51 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
end
end
- context "when the model doesn't have an ID column" do
+ context 'when the model specifies a primary_column_name' do
+ let!(:id1) { create(:container_expiration_policy).id }
+ let!(:id2) { create(:container_expiration_policy).id }
+ let!(:id3) { create(:container_expiration_policy).id }
+
+ around do |example|
+ freeze_time { example.run }
+ end
+
+ before do
+ ContainerExpirationPolicy.class_eval do
+ include EachBatch
+ end
+ end
+
+ it 'returns the final expected delay', :aggregate_failures do
+ Sidekiq::Testing.fake! do
+ final_delay = model.queue_background_migration_jobs_by_range_at_intervals(ContainerExpirationPolicy, 'FooJob', 10.minutes, batch_size: 2, primary_column_name: :project_id)
+
+ expect(final_delay.to_f).to eq(20.minutes.to_f)
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.minutes.from_now.to_f)
+ end
+ end
+
+ context "when the primary_column_name is not an integer" do
+ it 'raises error' do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(ContainerExpirationPolicy, 'FooJob', 10.minutes, primary_column_name: :enabled)
+ end.to raise_error(StandardError, /is not an integer column/)
+ end
+ end
+
+ context "when the primary_column_name does not exist" do
+ it 'raises error' do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(ContainerExpirationPolicy, 'FooJob', 10.minutes, primary_column_name: :foo)
+ end.to raise_error(StandardError, /does not have an ID column of foo/)
+ end
+ end
+ end
+
+ context "when the model doesn't have an ID or primary_column_name column" do
it 'raises error (for now)' do
expect do
model.queue_background_migration_jobs_by_range_at_intervals(ProjectAuthorization, 'FooJob', 10.seconds)
diff --git a/spec/lib/gitlab/database/postgres_hll/batch_distinct_counter_spec.rb b/spec/lib/gitlab/database/postgres_hll/batch_distinct_counter_spec.rb
new file mode 100644
index 00000000000..934e2274358
--- /dev/null
+++ b/spec/lib/gitlab/database/postgres_hll/batch_distinct_counter_spec.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::PostgresHll::BatchDistinctCounter do
+ let_it_be(:error_rate) { described_class::ERROR_RATE } # HyperLogLog is a probabilistic algorithm, which provides estimated data, with given error margin
+ let_it_be(:fallback) { ::Gitlab::Database::BatchCounter::FALLBACK }
+ let_it_be(:small_batch_size) { calculate_batch_size(described_class::MIN_REQUIRED_BATCH_SIZE) }
+ let(:model) { Issue }
+ let(:column) { :author_id }
+
+ let(:in_transaction) { false }
+
+ let_it_be(:user) { create(:user, email: 'email1@domain.com') }
+ let_it_be(:another_user) { create(:user, email: 'email2@domain.com') }
+
+ def calculate_batch_size(batch_size)
+ zero_offset_modifier = -1
+
+ batch_size + zero_offset_modifier
+ end
+
+ before do
+ allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(in_transaction)
+ end
+
+ context 'different distribution of relation records' do
+ [10, 100, 100_000].each do |spread|
+ context "records are spread within #{spread}" do
+ before do
+ ids = (1..spread).to_a.sample(10)
+ create_list(:issue, 10).each_with_index do |issue, i|
+ issue.id = ids[i]
+ end
+ end
+
+ it 'counts table' do
+ expect(described_class.new(model).estimate_distinct_count).to be_within(error_rate).percent_of(10)
+ end
+ end
+ end
+ end
+
+ context 'unit test for different counting parameters' do
+ before_all do
+ create_list(:issue, 3, author: user)
+ create_list(:issue, 2, author: another_user)
+ end
+
+ describe '#estimate_distinct_count' do
+ it 'counts table' do
+ expect(described_class.new(model).estimate_distinct_count).to be_within(error_rate).percent_of(5)
+ end
+
+ it 'counts with column field' do
+ expect(described_class.new(model, column).estimate_distinct_count).to be_within(error_rate).percent_of(2)
+ end
+
+ it 'counts with :id field' do
+ expect(described_class.new(model, :id).estimate_distinct_count).to be_within(error_rate).percent_of(5)
+ end
+
+ it 'counts with "id" field' do
+ expect(described_class.new(model, "id").estimate_distinct_count).to be_within(error_rate).percent_of(5)
+ end
+
+ it 'counts with table.column field' do
+ expect(described_class.new(model, "#{model.table_name}.#{column}").estimate_distinct_count).to be_within(error_rate).percent_of(2)
+ end
+
+ it 'counts with Arel column' do
+ expect(described_class.new(model, model.arel_table[column]).estimate_distinct_count).to be_within(error_rate).percent_of(2)
+ end
+
+ it 'counts over joined relations' do
+ expect(described_class.new(model.joins(:author), "users.email").estimate_distinct_count).to be_within(error_rate).percent_of(2)
+ end
+
+ it 'counts with :column field with batch_size of 50K' do
+ expect(described_class.new(model, column).estimate_distinct_count(batch_size: 50_000)).to be_within(error_rate).percent_of(2)
+ end
+
+ it 'will not count table with a batch size less than allowed' do
+ expect(described_class.new(model, column).estimate_distinct_count(batch_size: small_batch_size)).to eq(fallback)
+ end
+
+ it 'counts with different number of batches and aggregates total result' do
+ stub_const('Gitlab::Database::PostgresHll::BatchDistinctCounter::MIN_REQUIRED_BATCH_SIZE', 0)
+
+ [1, 2, 4, 5, 6].each { |i| expect(described_class.new(model).estimate_distinct_count(batch_size: i)).to be_within(error_rate).percent_of(5) }
+ end
+
+ it 'counts with a start and finish' do
+ expect(described_class.new(model, column).estimate_distinct_count(start: model.minimum(:id), finish: model.maximum(:id))).to be_within(error_rate).percent_of(2)
+ end
+
+ it "defaults the batch size to #{Gitlab::Database::PostgresHll::BatchDistinctCounter::DEFAULT_BATCH_SIZE}" do
+ min_id = model.minimum(:id)
+ batch_end_id = min_id + calculate_batch_size(Gitlab::Database::PostgresHll::BatchDistinctCounter::DEFAULT_BATCH_SIZE)
+
+ expect(model).to receive(:where).with("id" => min_id..batch_end_id).and_call_original
+
+ described_class.new(model).estimate_distinct_count
+ end
+
+ context 'when a transaction is open' do
+ let(:in_transaction) { true }
+
+ it 'raises an error' do
+ expect { described_class.new(model, column).estimate_distinct_count }.to raise_error('BatchCount can not be run inside a transaction')
+ end
+ end
+
+ context 'disallowed configurations' do
+ let(:default_batch_size) { Gitlab::Database::PostgresHll::BatchDistinctCounter::DEFAULT_BATCH_SIZE }
+
+ it 'returns fallback if start is bigger than finish' do
+ expect(described_class.new(model, column).estimate_distinct_count(start: 1, finish: 0)).to eq(fallback)
+ end
+
+ it 'returns fallback if data volume exceeds upper limit' do
+ large_finish = Gitlab::Database::PostgresHll::BatchDistinctCounter::MAX_DATA_VOLUME + 1
+ expect(described_class.new(model, column).estimate_distinct_count(start: 1, finish: large_finish)).to eq(fallback)
+ end
+
+ it 'returns fallback if batch size is less than min required' do
+ expect(described_class.new(model, column).estimate_distinct_count(batch_size: small_batch_size)).to eq(fallback)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/postgres_index_bloat_estimate_spec.rb b/spec/lib/gitlab/database/postgres_index_bloat_estimate_spec.rb
new file mode 100644
index 00000000000..da4422bd442
--- /dev/null
+++ b/spec/lib/gitlab/database/postgres_index_bloat_estimate_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::PostgresIndexBloatEstimate do
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ ANALYZE schema_migrations
+ SQL
+ end
+
+ subject { described_class.find(identifier) }
+
+ let(:identifier) { 'public.schema_migrations_pkey' }
+
+ describe '#bloat_size' do
+ it 'returns the bloat size in bytes' do
+ # We cannot reach much more about the bloat size estimate here
+ expect(subject.bloat_size).to be >= 0
+ end
+ end
+
+ describe '#bloat_size_bytes' do
+ it 'is an alias of #bloat_size' do
+ expect(subject.bloat_size_bytes).to eq(subject.bloat_size)
+ end
+ end
+
+ describe '#index' do
+ it 'belongs to a PostgresIndex' do
+ expect(subject.index.identifier).to eq(identifier)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/postgres_index_spec.rb b/spec/lib/gitlab/database/postgres_index_spec.rb
index d65b638f7bc..2fda9b85c5a 100644
--- a/spec/lib/gitlab/database/postgres_index_spec.rb
+++ b/spec/lib/gitlab/database/postgres_index_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe Gitlab::Database::PostgresIndex do
expect(described_class.regular).to all(have_attributes(unique: false))
end
- it 'only non partitioned indexes ' do
+ it 'only non partitioned indexes' do
expect(described_class.regular).to all(have_attributes(partitioned: false))
end
@@ -46,9 +46,24 @@ RSpec.describe Gitlab::Database::PostgresIndex do
end
end
- describe '.random_few' do
- it 'limits to two records by default' do
- expect(described_class.random_few(2).size).to eq(2)
+ describe '#bloat_size' do
+ subject { build(:postgres_index, bloat_estimate: bloat_estimate) }
+
+ let(:bloat_estimate) { build(:postgres_index_bloat_estimate) }
+ let(:bloat_size) { double }
+
+ it 'returns the bloat size from the estimate' do
+ expect(bloat_estimate).to receive(:bloat_size).and_return(bloat_size)
+
+ expect(subject.bloat_size).to eq(bloat_size)
+ end
+
+ context 'without a bloat estimate available' do
+ let(:bloat_estimate) { nil }
+
+ it 'returns 0' do
+ expect(subject.bloat_size).to eq(0)
+ end
end
end
diff --git a/spec/lib/gitlab/database/postgresql_adapter/empty_query_ping_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/empty_query_ping_spec.rb
new file mode 100644
index 00000000000..6e1e53e0e41
--- /dev/null
+++ b/spec/lib/gitlab/database/postgresql_adapter/empty_query_ping_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::PostgresqlAdapter::EmptyQueryPing do
+ describe '#active?' do
+ let(:adapter_class) do
+ Class.new do
+ include Gitlab::Database::PostgresqlAdapter::EmptyQueryPing
+
+ def initialize(connection, lock)
+ @connection = connection
+ @lock = lock
+ end
+ end
+ end
+
+ subject { adapter_class.new(connection, lock).active? }
+
+ let(:connection) { double(query: nil) }
+ let(:lock) { double }
+
+ before do
+ allow(lock).to receive(:synchronize).and_yield
+ end
+
+ it 'uses an empty query to check liveness' do
+ expect(connection).to receive(:query).with(';')
+
+ subject
+ end
+
+ it 'returns true if no error was signaled' do
+ expect(subject).to be_truthy
+ end
+
+ it 'returns false when an error occurs' do
+ expect(lock).to receive(:synchronize).and_raise(PG::Error)
+
+ expect(subject).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb b/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb
index 2d6765aac2e..51fc7c6620b 100644
--- a/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb
+++ b/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Database::Reindexing::ConcurrentReindex, '#perform' do
let(:table_name) { '_test_reindex_table' }
let(:column_name) { '_test_column' }
let(:index_name) { '_test_reindex_index' }
- let(:index) { instance_double(Gitlab::Database::PostgresIndex, indexrelid: 42, name: index_name, schema: 'public', partitioned?: false, unique?: false, exclusion?: false, definition: 'CREATE INDEX _test_reindex_index ON public._test_reindex_table USING btree (_test_column)') }
+ let(:index) { instance_double(Gitlab::Database::PostgresIndex, indexrelid: 42, name: index_name, schema: 'public', tablename: table_name, partitioned?: false, unique?: false, exclusion?: false, expression?: false, definition: 'CREATE INDEX _test_reindex_index ON public._test_reindex_table USING btree (_test_column)') }
let(:logger) { double('logger', debug: nil, info: nil, error: nil ) }
let(:connection) { ActiveRecord::Base.connection }
@@ -130,6 +130,36 @@ RSpec.describe Gitlab::Database::Reindexing::ConcurrentReindex, '#perform' do
check_index_exists
end
+ context 'for expression indexes' do
+ before do
+ allow(index).to receive(:expression?).and_return(true)
+ end
+
+ it 'rebuilds table statistics before dropping the original index' do
+ expect(connection).to receive(:execute).with('SET statement_timeout TO \'21600s\'').twice
+
+ expect_to_execute_concurrently_in_order(create_index)
+
+ expect_to_execute_concurrently_in_order(<<~SQL)
+ ANALYZE "#{index.schema}"."#{index.tablename}"
+ SQL
+
+ expect_next_instance_of(::Gitlab::Database::WithLockRetries) do |instance|
+ expect(instance).to receive(:run).with(raise_on_exhaustion: true).and_yield
+ end
+
+ expect_index_rename(index.name, replaced_name)
+ expect_index_rename(replacement_name, index.name)
+ expect_index_rename(replaced_name, replacement_name)
+
+ expect_to_execute_concurrently_in_order(drop_index)
+
+ subject.perform
+
+ check_index_exists
+ end
+ end
+
context 'when a dangling index is left from a previous run' do
before do
connection.execute("CREATE INDEX #{replacement_name} ON #{table_name} (#{column_name})")
diff --git a/spec/lib/gitlab/database/reindexing/index_selection_spec.rb b/spec/lib/gitlab/database/reindexing/index_selection_spec.rb
new file mode 100644
index 00000000000..a5e2f368f40
--- /dev/null
+++ b/spec/lib/gitlab/database/reindexing/index_selection_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Reindexing::IndexSelection do
+ include DatabaseHelpers
+
+ subject { described_class.new(Gitlab::Database::PostgresIndex.all).to_a }
+
+ before do
+ swapout_view_for_table(:postgres_index_bloat_estimates)
+ swapout_view_for_table(:postgres_indexes)
+ end
+
+ def execute(sql)
+ ActiveRecord::Base.connection.execute(sql)
+ end
+
+ it 'orders by highest bloat first' do
+ create_list(:postgres_index, 10).each_with_index do |index, i|
+ create(:postgres_index_bloat_estimate, index: index, bloat_size_bytes: 1.megabyte * i)
+ end
+
+ expected = Gitlab::Database::PostgresIndexBloatEstimate.order(bloat_size_bytes: :desc).map(&:index)
+
+ expect(subject).to eq(expected)
+ end
+
+ context 'with time frozen' do
+ around do |example|
+ freeze_time { example.run }
+ end
+
+ it 'does not return indexes with reindex action in the last 7 days' do
+ not_recently_reindexed = create_list(:postgres_index, 2).each_with_index do |index, i|
+ create(:postgres_index_bloat_estimate, index: index, bloat_size_bytes: 1.megabyte * i)
+ create(:reindex_action, index: index, action_end: Time.zone.now - 7.days - 1.minute)
+ end
+
+ create_list(:postgres_index, 2).each_with_index do |index, i|
+ create(:postgres_index_bloat_estimate, index: index, bloat_size_bytes: 1.megabyte * i)
+ create(:reindex_action, index: index, action_end: Time.zone.now)
+ end
+
+ expected = Gitlab::Database::PostgresIndexBloatEstimate.where(identifier: not_recently_reindexed.map(&:identifier)).map(&:index).map(&:identifier).sort
+
+ expect(subject.map(&:identifier).sort).to eq(expected)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb b/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb
index efb5b8463a1..225f23d2135 100644
--- a/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb
+++ b/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::Reindexing::ReindexAction, '.keep_track_of' do
- let(:index) { double('index', identifier: 'public.something', ondisk_size_bytes: 10240, reload: nil) }
+ let(:index) { double('index', identifier: 'public.something', ondisk_size_bytes: 10240, reload: nil, bloat_size: 42) }
let(:size_after) { 512 }
it 'yields to the caller' do
@@ -47,6 +47,12 @@ RSpec.describe Gitlab::Database::Reindexing::ReindexAction, '.keep_track_of' do
expect(find_record.ondisk_size_bytes_end).to eq(size_after)
end
+ it 'creates the record with the indexes bloat estimate' do
+ described_class.keep_track_of(index) do
+ expect(find_record.bloat_estimate_bytes_start).to eq(index.bloat_size)
+ end
+ end
+
context 'in case of errors' do
it 'sets the state to failed' do
expect do
diff --git a/spec/lib/gitlab/database/reindexing_spec.rb b/spec/lib/gitlab/database/reindexing_spec.rb
index 359e0597f4e..eb78a5fe8ea 100644
--- a/spec/lib/gitlab/database/reindexing_spec.rb
+++ b/spec/lib/gitlab/database/reindexing_spec.rb
@@ -6,12 +6,16 @@ RSpec.describe Gitlab::Database::Reindexing do
include ExclusiveLeaseHelpers
describe '.perform' do
- subject { described_class.perform(indexes) }
+ subject { described_class.perform(candidate_indexes) }
let(:coordinator) { instance_double(Gitlab::Database::Reindexing::Coordinator) }
+ let(:index_selection) { instance_double(Gitlab::Database::Reindexing::IndexSelection) }
+ let(:candidate_indexes) { double }
let(:indexes) { double }
it 'delegates to Coordinator' do
+ expect(Gitlab::Database::Reindexing::IndexSelection).to receive(:new).with(candidate_indexes).and_return(index_selection)
+ expect(index_selection).to receive(:take).with(2).and_return(indexes)
expect(Gitlab::Database::Reindexing::Coordinator).to receive(:new).with(indexes).and_return(coordinator)
expect(coordinator).to receive(:perform)
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index ca9f9ab915f..4048fc69591 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -118,8 +118,8 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
expect(result[:status]).to eq(:success)
expect(project.name).to eq(described_class::PROJECT_NAME)
expect(project.description).to eq(
- 'This project is automatically generated and will be used to help monitor this GitLab instance. ' \
- "[More information](#{docs_path})"
+ 'This project is automatically generated and helps monitor this GitLab instance. ' \
+ "[Learn more](#{docs_path})."
)
expect(File).to exist("doc/#{path}.md")
end
diff --git a/spec/lib/gitlab/deploy_key_access_spec.rb b/spec/lib/gitlab/deploy_key_access_spec.rb
new file mode 100644
index 00000000000..e186e993d8f
--- /dev/null
+++ b/spec/lib/gitlab/deploy_key_access_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::DeployKeyAccess do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:deploy_key) { create(:deploy_key, user: user) }
+ let(:project) { create(:project, :repository) }
+ let(:protected_branch) { create(:protected_branch, :no_one_can_push, project: project) }
+
+ subject(:access) { described_class.new(deploy_key, container: project) }
+
+ before do
+ project.add_guest(user)
+ create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key)
+ end
+
+ describe '#can_create_tag?' do
+ context 'push tag that matches a protected tag pattern via a deploy key' do
+ it 'still pushes that tag' do
+ create(:protected_tag, project: project, name: 'v*')
+
+ expect(access.can_create_tag?('v0.1.2')).to be_truthy
+ end
+ end
+ end
+
+ describe '#can_push_for_ref?' do
+ context 'push to a protected branch of this project via a deploy key' do
+ before do
+ create(:protected_branch_push_access_level, protected_branch: protected_branch, deploy_key: deploy_key)
+ end
+
+ context 'when the project has active deploy key owned by this user' do
+ it 'returns true' do
+ expect(access.can_push_for_ref?(protected_branch.name)).to be_truthy
+ end
+ end
+
+ context 'when the project has active deploy keys, but not by this user' do
+ let(:deploy_key) { create(:deploy_key, user: create(:user)) }
+
+ it 'returns false' do
+ expect(access.can_push_for_ref?(protected_branch.name)).to be_falsey
+ end
+ end
+
+ context 'when there is another branch no one can push to' do
+ let(:another_branch) { create(:protected_branch, :no_one_can_push, name: 'another_branch', project: project) }
+
+ it 'returns false when trying to push to that other branch' do
+ expect(access.can_push_for_ref?(another_branch.name)).to be_falsey
+ end
+
+ context 'and the deploy key added for the first protected branch is also added for this other branch' do
+ it 'returns true for both protected branches' do
+ create(:protected_branch_push_access_level, protected_branch: another_branch, deploy_key: deploy_key)
+
+ expect(access.can_push_for_ref?(protected_branch.name)).to be_truthy
+ expect(access.can_push_for_ref?(another_branch.name)).to be_truthy
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/file_collection/commit_spec.rb b/spec/lib/gitlab/diff/file_collection/commit_spec.rb
index 7773604a638..3d995b36b6f 100644
--- a/spec/lib/gitlab/diff/file_collection/commit_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/commit_spec.rb
@@ -4,17 +4,75 @@ require 'spec_helper'
RSpec.describe Gitlab::Diff::FileCollection::Commit do
let(:project) { create(:project, :repository) }
+ let(:diffable) { project.commit }
- it_behaves_like 'diff statistics' do
- let(:collection_default_args) do
- { diff_options: {} }
- end
+ let(:collection_default_args) do
+ { diff_options: {} }
+ end
- let(:diffable) { project.commit }
+ it_behaves_like 'diff statistics' do
let(:stub_path) { 'bar/branch-test.txt' }
end
- it_behaves_like 'unfoldable diff' do
- let(:diffable) { project.commit }
+ it_behaves_like 'unfoldable diff'
+
+ it_behaves_like 'sortable diff files' do
+ let(:diffable) { project.commit('913c66a') }
+
+ let(:unsorted_diff_files_paths) do
+ [
+ '.DS_Store',
+ 'CHANGELOG',
+ 'MAINTENANCE.md',
+ 'PROCESS.md',
+ 'VERSION',
+ 'encoding/feature-1.txt',
+ 'encoding/feature-2.txt',
+ 'encoding/hotfix-1.txt',
+ 'encoding/hotfix-2.txt',
+ 'encoding/russian.rb',
+ 'encoding/test.txt',
+ 'encoding/テスト.txt',
+ 'encoding/テスト.xls',
+ 'files/.DS_Store',
+ 'files/html/500.html',
+ 'files/images/logo-black.png',
+ 'files/images/logo-white.png',
+ 'files/js/application.js',
+ 'files/js/commit.js.coffee',
+ 'files/markdown/ruby-style-guide.md',
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/ruby/version_info.rb'
+ ]
+ end
+
+ let(:sorted_diff_files_paths) do
+ [
+ 'encoding/feature-1.txt',
+ 'encoding/feature-2.txt',
+ 'encoding/hotfix-1.txt',
+ 'encoding/hotfix-2.txt',
+ 'encoding/russian.rb',
+ 'encoding/test.txt',
+ 'encoding/テスト.txt',
+ 'encoding/テスト.xls',
+ 'files/html/500.html',
+ 'files/images/logo-black.png',
+ 'files/images/logo-white.png',
+ 'files/js/application.js',
+ 'files/js/commit.js.coffee',
+ 'files/markdown/ruby-style-guide.md',
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/ruby/version_info.rb',
+ 'files/.DS_Store',
+ '.DS_Store',
+ 'CHANGELOG',
+ 'MAINTENANCE.md',
+ 'PROCESS.md',
+ 'VERSION'
+ ]
+ end
end
end
diff --git a/spec/lib/gitlab/diff/file_collection/compare_spec.rb b/spec/lib/gitlab/diff/file_collection/compare_spec.rb
index dda4513a3a1..f3326f4f03d 100644
--- a/spec/lib/gitlab/diff/file_collection/compare_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/compare_spec.rb
@@ -27,4 +27,43 @@ RSpec.describe Gitlab::Diff::FileCollection::Compare do
let(:diffable) { Compare.new(raw_compare, project) }
let(:stub_path) { '.gitignore' }
end
+
+ it_behaves_like 'sortable diff files' do
+ let(:diffable) { Compare.new(raw_compare, project) }
+ let(:collection_default_args) do
+ {
+ project: diffable.project,
+ diff_options: {},
+ diff_refs: diffable.diff_refs
+ }
+ end
+
+ let(:unsorted_diff_files_paths) do
+ [
+ '.DS_Store',
+ '.gitignore',
+ '.gitmodules',
+ 'Gemfile.zip',
+ 'files/.DS_Store',
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/ruby/version_info.rb',
+ 'gitlab-shell'
+ ]
+ end
+
+ let(:sorted_diff_files_paths) do
+ [
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/ruby/version_info.rb',
+ 'files/.DS_Store',
+ '.DS_Store',
+ '.gitignore',
+ '.gitmodules',
+ 'Gemfile.zip',
+ 'gitlab-shell'
+ ]
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
index 72a66b0451e..670c734ce08 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
@@ -18,6 +18,10 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
let(:diff_files) { subject.diff_files }
+ before do
+ stub_feature_flags(diffs_gradual_load: false)
+ end
+
describe 'initialize' do
it 'memoizes pagination_data' do
expect(subject.pagination_data).to eq(current_page: 1, next_page: 2, total_pages: 2)
@@ -97,6 +101,18 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
expect(collection.diff_files.map(&:new_path)).to eq(expected_batch_files)
end
end
+
+ context 'with diffs gradual load feature flag enabled' do
+ let(:batch_page) { 0 }
+
+ before do
+ stub_feature_flags(diffs_gradual_load: true)
+ end
+
+ it 'returns correct diff files' do
+ expect(subject.diffs.map(&:new_path)).to eq(diff_files_relation.page(1).per(batch_size).map(&:new_path))
+ end
+ end
end
it_behaves_like 'unfoldable diff' do
@@ -114,6 +130,7 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
end
let(:diffable) { merge_request.merge_request_diff }
+ let(:batch_page) { 2 }
let(:stub_path) { '.gitignore' }
subject do
@@ -127,4 +144,18 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
it_behaves_like 'cacheable diff collection' do
let(:cacheable_files_count) { batch_size }
end
+
+ it_behaves_like 'unsortable diff files' do
+ let(:diffable) { merge_request.merge_request_diff }
+ let(:collection_default_args) do
+ { diff_options: {} }
+ end
+
+ subject do
+ described_class.new(merge_request.merge_request_diff,
+ batch_page,
+ batch_size,
+ **collection_default_args)
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index 429e552278d..03a9b9bd21e 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -54,4 +54,11 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiff do
it 'returns a valid instance of a DiffCollection' do
expect(diff_files).to be_a(Gitlab::Git::DiffCollection)
end
+
+ it_behaves_like 'unsortable diff files' do
+ let(:diffable) { merge_request.merge_request_diff }
+ let(:collection_default_args) do
+ { diff_options: {} }
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/file_collection_sorter_spec.rb b/spec/lib/gitlab/diff/file_collection_sorter_spec.rb
new file mode 100644
index 00000000000..8822fc55c6e
--- /dev/null
+++ b/spec/lib/gitlab/diff/file_collection_sorter_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Diff::FileCollectionSorter do
+ let(:diffs) do
+ [
+ double(new_path: '.dir/test', old_path: '.dir/test'),
+ double(new_path: '', old_path: '.file'),
+ double(new_path: '1-folder/A-file.ext', old_path: '1-folder/A-file.ext'),
+ double(new_path: nil, old_path: '1-folder/M-file.ext'),
+ double(new_path: '1-folder/Z-file.ext', old_path: '1-folder/Z-file.ext'),
+ double(new_path: '', old_path: '1-folder/nested/A-file.ext'),
+ double(new_path: '1-folder/nested/M-file.ext', old_path: '1-folder/nested/M-file.ext'),
+ double(new_path: nil, old_path: '1-folder/nested/Z-file.ext'),
+ double(new_path: '2-folder/A-file.ext', old_path: '2-folder/A-file.ext'),
+ double(new_path: '', old_path: '2-folder/M-file.ext'),
+ double(new_path: '2-folder/Z-file.ext', old_path: '2-folder/Z-file.ext'),
+ double(new_path: nil, old_path: '2-folder/nested/A-file.ext'),
+ double(new_path: 'A-file.ext', old_path: 'A-file.ext'),
+ double(new_path: '', old_path: 'M-file.ext'),
+ double(new_path: 'Z-file.ext', old_path: 'Z-file.ext')
+ ]
+ end
+
+ subject { described_class.new(diffs) }
+
+ describe '#sort' do
+ let(:sorted_files_paths) { subject.sort.map { |file| file.new_path.presence || file.old_path } }
+
+ it 'returns list sorted directory first' do
+ expect(sorted_files_paths).to eq([
+ '.dir/test',
+ '1-folder/nested/A-file.ext',
+ '1-folder/nested/M-file.ext',
+ '1-folder/nested/Z-file.ext',
+ '1-folder/A-file.ext',
+ '1-folder/M-file.ext',
+ '1-folder/Z-file.ext',
+ '2-folder/nested/A-file.ext',
+ '2-folder/A-file.ext',
+ '2-folder/M-file.ext',
+ '2-folder/Z-file.ext',
+ '.file',
+ 'A-file.ext',
+ 'M-file.ext',
+ 'Z-file.ext'
+ ])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
index b891f9e8285..4163c0eced5 100644
--- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb
+++ b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
@@ -188,7 +188,7 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
let(:old_blob) { Blob.decorate(Gitlab::Git::Blob.new(data: raw_old_blob, size: 10)) }
let(:diff) do
- Gitlab::Git::Diff.new(diff: raw_diff,
+ Gitlab::Git::Diff.new({ diff: raw_diff,
new_path: "build-aux/flatpak/org.gnome.Nautilus.json",
old_path: "build-aux/flatpak/org.gnome.Nautilus.json",
a_mode: "100644",
@@ -196,7 +196,7 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_file: false,
renamed_file: false,
deleted_file: false,
- too_large: false)
+ too_large: false })
end
let(:diff_file) do
diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
index 2ebfb054a96..32b451f8329 100644
--- a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
@@ -254,7 +254,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
new_issue = Issue.last
- expect(new_issue.service_desk_reply_to).to eq('finn@adventuretime.ooo')
+ expect(new_issue.external_author).to eq('finn@adventuretime.ooo')
end
end
diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb
index 575ff7f357b..bc4c6cf007d 100644
--- a/spec/lib/gitlab/email/reply_parser_spec.rb
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -6,7 +6,7 @@ require "spec_helper"
RSpec.describe Gitlab::Email::ReplyParser do
describe '#execute' do
def test_parse_body(mail_string, params = {})
- described_class.new(Mail::Message.new(mail_string), params).execute
+ described_class.new(Mail::Message.new(mail_string), **params).execute
end
it "returns an empty string if the message is blank" do
diff --git a/spec/lib/gitlab/email/smime/certificate_spec.rb b/spec/lib/gitlab/email/smime/certificate_spec.rb
index e4a085d971b..f7bb933e348 100644
--- a/spec/lib/gitlab/email/smime/certificate_spec.rb
+++ b/spec/lib/gitlab/email/smime/certificate_spec.rb
@@ -69,8 +69,8 @@ RSpec.describe Gitlab::Email::Smime::Certificate do
describe '.from_files' do
it 'parses correctly a certificate and key' do
- allow(File).to receive(:read).with('a_key').and_return(@cert[:key].to_s)
- allow(File).to receive(:read).with('a_cert').and_return(@cert[:cert].to_pem)
+ stub_file_read('a_key', content: @cert[:key].to_s)
+ stub_file_read('a_cert', content: @cert[:cert].to_pem)
parsed_cert = described_class.from_files('a_key', 'a_cert')
@@ -79,9 +79,9 @@ RSpec.describe Gitlab::Email::Smime::Certificate do
context 'with optional ca_certs' do
it 'parses correctly certificate, key and ca_certs' do
- allow(File).to receive(:read).with('a_key').and_return(@cert[:key].to_s)
- allow(File).to receive(:read).with('a_cert').and_return(@cert[:cert].to_pem)
- allow(File).to receive(:read).with('a_ca_cert').and_return(@intermediate_ca[:cert].to_pem)
+ stub_file_read('a_key', content: @cert[:key].to_s)
+ stub_file_read('a_cert', content: @cert[:cert].to_pem)
+ stub_file_read('a_ca_cert', content: @intermediate_ca[:cert].to_pem)
parsed_cert = described_class.from_files('a_key', 'a_cert', 'a_ca_cert')
@@ -94,8 +94,8 @@ RSpec.describe Gitlab::Email::Smime::Certificate do
it 'parses correctly a certificate and key' do
cert = generate_cert(signer_ca: @root_ca)
- allow(File).to receive(:read).with('a_key').and_return(cert[:key].to_s)
- allow(File).to receive(:read).with('a_cert').and_return(cert[:cert].to_pem)
+ stub_file_read('a_key', content: cert[:key].to_s)
+ stub_file_read('a_cert', content: cert[:cert].to_pem)
parsed_cert = described_class.from_files('a_key', 'a_cert')
diff --git a/spec/lib/gitlab/encrypted_configuration_spec.rb b/spec/lib/gitlab/encrypted_configuration_spec.rb
new file mode 100644
index 00000000000..eadc2cf71a7
--- /dev/null
+++ b/spec/lib/gitlab/encrypted_configuration_spec.rb
@@ -0,0 +1,145 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Gitlab::EncryptedConfiguration do
+ subject(:configuration) { described_class.new }
+
+ let!(:config_tmp_dir) { Dir.mktmpdir('config-') }
+
+ after do
+ FileUtils.rm_f(config_tmp_dir)
+ end
+
+ describe '#initialize' do
+ it 'accepts all args as optional fields' do
+ expect { configuration }.not_to raise_exception
+
+ expect(configuration.key).to be_nil
+ expect(configuration.previous_keys).to be_empty
+ end
+
+ it 'generates 32 byte key when provided a larger base key' do
+ configuration = described_class.new(base_key: 'A' * 64)
+
+ expect(configuration.key.bytesize).to eq 32
+ end
+
+ it 'generates 32 byte key when provided a smaller base key' do
+ configuration = described_class.new(base_key: 'A' * 16)
+
+ expect(configuration.key.bytesize).to eq 32
+ end
+
+ it 'throws an error when the base key is too small' do
+ expect { described_class.new(base_key: 'A' * 12) }.to raise_error 'Base key too small'
+ end
+ end
+
+ context 'when provided a config file but no key' do
+ let(:config_path) { File.join(config_tmp_dir, 'credentials.yml.enc') }
+
+ it 'throws an error when writing without a key' do
+ expect { described_class.new(content_path: config_path).write('test') }.to raise_error Gitlab::EncryptedConfiguration::MissingKeyError
+ end
+
+ it 'throws an error when reading without a key' do
+ config = described_class.new(content_path: config_path)
+ File.write(config_path, 'test')
+ expect { config.read }.to raise_error Gitlab::EncryptedConfiguration::MissingKeyError
+ end
+ end
+
+ context 'when provided key and config file' do
+ let(:credentials_config_path) { File.join(config_tmp_dir, 'credentials.yml.enc') }
+ let(:credentials_key) { SecureRandom.hex(64) }
+
+ describe '#write' do
+ it 'encrypts the file using the provided key' do
+ encryptor = ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(credentials_key), cipher: 'aes-256-gcm')
+ config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
+
+ config.write('sample-content')
+ expect(encryptor.decrypt_and_verify(File.read(credentials_config_path))).to eq('sample-content')
+ end
+ end
+
+ describe '#read' do
+ it 'reads yaml configuration' do
+ config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
+
+ config.write({ foo: { bar: true } }.to_yaml)
+ expect(config[:foo][:bar]).to be true
+ end
+
+ it 'allows referencing top level keys via dot syntax' do
+ config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
+
+ config.write({ foo: { bar: true } }.to_yaml)
+ expect(config.foo[:bar]).to be true
+ end
+
+ it 'throws a custom error when referencing an invalid key map config' do
+ config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
+
+ config.write("stringcontent")
+ expect { config[:foo] }.to raise_error Gitlab::EncryptedConfiguration::InvalidConfigError
+ end
+ end
+
+ describe '#change' do
+ it 'changes yaml configuration' do
+ config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
+
+ config.write({ foo: { bar: true } }.to_yaml)
+ config.change do |unencrypted_contents|
+ contents = YAML.safe_load(unencrypted_contents, permitted_classes: [Symbol])
+ contents.merge(beef: "stew").to_yaml
+ end
+ expect(config.foo[:bar]).to be true
+ expect(config.beef).to eq('stew')
+ end
+ end
+
+ context 'when provided previous_keys for rotation' do
+ let(:credential_key_original) { SecureRandom.hex(64) }
+ let(:credential_key_latest) { SecureRandom.hex(64) }
+ let(:config_path_original) { File.join(config_tmp_dir, 'credentials-orig.yml.enc') }
+ let(:config_path_latest) { File.join(config_tmp_dir, 'credentials-latest.yml.enc') }
+
+ def encryptor(key)
+ ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(key), cipher: 'aes-256-gcm')
+ end
+
+ describe '#write' do
+ it 'rotates the key when provided a new key' do
+ config1 = described_class.new(content_path: config_path_original, base_key: credential_key_original)
+ config1.write('sample-content1')
+
+ config2 = described_class.new(content_path: config_path_latest, base_key: credential_key_latest, previous_keys: [credential_key_original])
+ config2.write('sample-content2')
+
+ original_key_encryptor = encryptor(credential_key_original) # can read with the initial key
+ latest_key_encryptor = encryptor(credential_key_latest) # can read with the new key
+ both_key_encryptor = encryptor(credential_key_latest) # can read with either key
+ both_key_encryptor.rotate(Gitlab::EncryptedConfiguration.generate_key(credential_key_original))
+
+ expect(original_key_encryptor.decrypt_and_verify(File.read(config_path_original))).to eq('sample-content1')
+ expect(both_key_encryptor.decrypt_and_verify(File.read(config_path_original))).to eq('sample-content1')
+ expect(latest_key_encryptor.decrypt_and_verify(File.read(config_path_latest))).to eq('sample-content2')
+ expect(both_key_encryptor.decrypt_and_verify(File.read(config_path_latest))).to eq('sample-content2')
+ expect { original_key_encryptor.decrypt_and_verify(File.read(config_path_latest)) }.to raise_error(ActiveSupport::MessageEncryptor::InvalidMessage)
+ end
+ end
+
+ describe '#read' do
+ it 'supports reading using rotated config' do
+ described_class.new(content_path: config_path_original, base_key: credential_key_original).write({ foo: { bar: true } }.to_yaml)
+
+ config = described_class.new(content_path: config_path_original, base_key: credential_key_latest, previous_keys: [credential_key_original])
+ expect(config[:foo][:bar]).to be true
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/experimentation/controller_concern_spec.rb b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
index 2fe3d36daf7..03cb89ee033 100644
--- a/spec/lib/gitlab/experimentation/controller_concern_spec.rb
+++ b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
@@ -6,12 +6,10 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
before do
stub_const('Gitlab::Experimentation::EXPERIMENTS', {
backwards_compatible_test_experiment: {
- environment: environment,
tracking_category: 'Team',
use_backwards_compatible_subject_index: true
},
test_experiment: {
- environment: environment,
tracking_category: 'Team'
}
}
@@ -21,7 +19,6 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
Feature.enable_percentage_of_time(:test_experiment_experiment_percentage, enabled_percentage)
end
- let(:environment) { Rails.env.test? }
let(:enabled_percentage) { 10 }
controller(ApplicationController) do
@@ -78,29 +75,24 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
describe '#push_frontend_experiment' do
it 'pushes an experiment to the frontend' do
gon = instance_double('gon')
- experiments = { experiments: { 'myExperiment' => true } }
-
- stub_experiment_for_user(my_experiment: true)
+ stub_experiment_for_subject(my_experiment: true)
allow(controller).to receive(:gon).and_return(gon)
- expect(gon).to receive(:push).with(experiments, true)
+ expect(gon).to receive(:push).with({ experiments: { 'myExperiment' => true } }, true)
controller.push_frontend_experiment(:my_experiment)
end
end
describe '#experiment_enabled?' do
- def check_experiment(exp_key = :test_experiment)
- controller.experiment_enabled?(exp_key)
+ def check_experiment(exp_key = :test_experiment, subject = nil)
+ controller.experiment_enabled?(exp_key, subject: subject)
end
subject { check_experiment }
context 'cookie is not present' do
- it 'calls Gitlab::Experimentation.enabled_for_value? with the name of the experiment and an experimentation_subject_index of nil' do
- expect(Gitlab::Experimentation).to receive(:enabled_for_value?).with(:test_experiment, nil)
- check_experiment
- end
+ it { is_expected.to eq(false) }
end
context 'cookie is present' do
@@ -112,37 +104,56 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
end
where(:experiment_key, :index_value) do
- :test_experiment | 40 # Zlib.crc32('test_experimentabcd-1234') % 100 = 40
- :backwards_compatible_test_experiment | 76 # 'abcd1234'.hex % 100 = 76
+ :test_experiment | 'abcd-1234'
+ :backwards_compatible_test_experiment | 'abcd1234'
end
with_them do
- it 'calls Gitlab::Experimentation.enabled_for_value? with the name of the experiment and the calculated experimentation_subject_index based on the uuid' do
- expect(Gitlab::Experimentation).to receive(:enabled_for_value?).with(experiment_key, index_value)
+ it 'calls Gitlab::Experimentation.in_experiment_group?? with the name of the experiment and the calculated experimentation_subject_index based on the uuid' do
+ expect(Gitlab::Experimentation).to receive(:in_experiment_group?).with(experiment_key, subject: index_value)
+
check_experiment(experiment_key)
end
end
- end
- it 'returns true when DNT: 0 is set in the request' do
- allow(Gitlab::Experimentation).to receive(:enabled_for_value?) { true }
- controller.request.headers['DNT'] = '0'
+ context 'when subject is given' do
+ let(:user) { build(:user) }
+
+ it 'uses the subject' do
+ expect(Gitlab::Experimentation).to receive(:in_experiment_group?).with(:test_experiment, subject: user)
- is_expected.to be_truthy
+ check_experiment(:test_experiment, user)
+ end
+ end
end
- it 'returns false when DNT: 1 is set in the request' do
- allow(Gitlab::Experimentation).to receive(:enabled_for_value?) { true }
- controller.request.headers['DNT'] = '1'
+ context 'do not track' do
+ before do
+ allow(Gitlab::Experimentation).to receive(:in_experiment_group?) { true }
+ end
+
+ context 'when do not track is disabled' do
+ before do
+ controller.request.headers['DNT'] = '0'
+ end
+
+ it { is_expected.to eq(true) }
+ end
- is_expected.to be_falsy
+ context 'when do not track is enabled' do
+ before do
+ controller.request.headers['DNT'] = '1'
+ end
+
+ it { is_expected.to eq(false) }
+ end
end
- describe 'URL parameter to force enable experiment' do
+ context 'URL parameter to force enable experiment' do
it 'returns true unconditionally' do
get :index, params: { force_experiment: :test_experiment }
- is_expected.to be_truthy
+ is_expected.to eq(true)
end
end
end
@@ -155,7 +166,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
context 'the user is part of the experimental group' do
before do
- stub_experiment_for_user(test_experiment: true)
+ stub_experiment_for_subject(test_experiment: true)
end
it 'tracks the event with the right parameters' do
@@ -172,7 +183,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
context 'the user is part of the control group' do
before do
- stub_experiment_for_user(test_experiment: false)
+ stub_experiment_for_subject(test_experiment: false)
end
it 'tracks the event with the right parameters' do
@@ -215,6 +226,59 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
expect_no_snowplow_event
end
end
+
+ context 'subject is provided' do
+ before do
+ stub_experiment_for_subject(test_experiment: false)
+ end
+
+ it "provides the subject's hashed global_id as label" do
+ experiment_subject = double(:subject, to_global_id: 'abc')
+
+ controller.track_experiment_event(:test_experiment, 'start', 1, subject: experiment_subject)
+
+ expect_snowplow_event(
+ category: 'Team',
+ action: 'start',
+ property: 'control_group',
+ value: 1,
+ label: Digest::MD5.hexdigest('abc')
+ )
+ end
+
+ it "provides the subject's hashed string representation as label" do
+ experiment_subject = 'somestring'
+
+ controller.track_experiment_event(:test_experiment, 'start', 1, subject: experiment_subject)
+
+ expect_snowplow_event(
+ category: 'Team',
+ action: 'start',
+ property: 'control_group',
+ value: 1,
+ label: Digest::MD5.hexdigest('somestring')
+ )
+ end
+ end
+
+ context 'no subject is provided but cookie is set' do
+ before do
+ get :index
+ stub_experiment_for_subject(test_experiment: false)
+ end
+
+ it 'uses the experimentation_subject_id as fallback' do
+ controller.track_experiment_event(:test_experiment, 'start', 1)
+
+ expect_snowplow_event(
+ category: 'Team',
+ action: 'start',
+ property: 'control_group',
+ value: 1,
+ label: cookies.permanent.signed[:experimentation_subject_id]
+ )
+ end
+ end
end
context 'when the experiment is disabled' do
@@ -238,7 +302,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
context 'the user is part of the experimental group' do
before do
- stub_experiment_for_user(test_experiment: true)
+ stub_experiment_for_subject(test_experiment: true)
end
it 'pushes the right parameters to gon' do
@@ -256,9 +320,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
context 'the user is part of the control group' do
before do
- allow_next_instance_of(described_class) do |instance|
- allow(instance).to receive(:experiment_enabled?).with(:test_experiment).and_return(false)
- end
+ stub_experiment_for_subject(test_experiment: false)
end
it 'pushes the right parameters to gon' do
@@ -311,7 +373,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
it 'does not push data to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start')
- expect(Gon.method_defined?(:tracking_data)).to be_falsey
+ expect(Gon.method_defined?(:tracking_data)).to eq(false)
end
end
end
@@ -322,7 +384,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
end
it 'does not push data to gon' do
- expect(Gon.method_defined?(:tracking_data)).to be_falsey
+ expect(Gon.method_defined?(:tracking_data)).to eq(false)
controller.track_experiment_event(:test_experiment, 'start')
end
end
@@ -330,6 +392,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
describe '#record_experiment_user' do
let(:user) { build(:user) }
+ let(:context) { { a: 42 } }
context 'when the experiment is enabled' do
before do
@@ -339,27 +402,25 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
context 'the user is part of the experimental group' do
before do
- stub_experiment_for_user(test_experiment: true)
+ stub_experiment_for_subject(test_experiment: true)
end
it 'calls add_user on the Experiment model' do
- expect(::Experiment).to receive(:add_user).with(:test_experiment, :experimental, user)
+ expect(::Experiment).to receive(:add_user).with(:test_experiment, :experimental, user, context)
- controller.record_experiment_user(:test_experiment)
+ controller.record_experiment_user(:test_experiment, context)
end
end
context 'the user is part of the control group' do
before do
- allow_next_instance_of(described_class) do |instance|
- allow(instance).to receive(:experiment_enabled?).with(:test_experiment).and_return(false)
- end
+ stub_experiment_for_subject(test_experiment: false)
end
it 'calls add_user on the Experiment model' do
- expect(::Experiment).to receive(:add_user).with(:test_experiment, :control, user)
+ expect(::Experiment).to receive(:add_user).with(:test_experiment, :control, user, context)
- controller.record_experiment_user(:test_experiment)
+ controller.record_experiment_user(:test_experiment, context)
end
end
end
@@ -373,7 +434,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
it 'does not call add_user on the Experiment model' do
expect(::Experiment).not_to receive(:add_user)
- controller.record_experiment_user(:test_experiment)
+ controller.record_experiment_user(:test_experiment, context)
end
end
@@ -385,27 +446,26 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
it 'does not call add_user on the Experiment model' do
expect(::Experiment).not_to receive(:add_user)
- controller.record_experiment_user(:test_experiment)
+ controller.record_experiment_user(:test_experiment, context)
end
end
context 'do not track' do
before do
+ stub_experiment(test_experiment: true)
allow(controller).to receive(:current_user).and_return(user)
- allow_next_instance_of(described_class) do |instance|
- allow(instance).to receive(:experiment_enabled?).with(:test_experiment).and_return(false)
- end
end
context 'is disabled' do
before do
request.headers['DNT'] = '0'
+ stub_experiment_for_subject(test_experiment: false)
end
it 'calls add_user on the Experiment model' do
- expect(::Experiment).to receive(:add_user).with(:test_experiment, :control, user)
+ expect(::Experiment).to receive(:add_user).with(:test_experiment, :control, user, context)
- controller.record_experiment_user(:test_experiment)
+ controller.record_experiment_user(:test_experiment, context)
end
end
@@ -417,12 +477,62 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
it 'does not call add_user on the Experiment model' do
expect(::Experiment).not_to receive(:add_user)
- controller.record_experiment_user(:test_experiment)
+ controller.record_experiment_user(:test_experiment, context)
end
end
end
end
+ describe '#record_experiment_conversion_event' do
+ let(:user) { build(:user) }
+
+ before do
+ allow(controller).to receive(:dnt_enabled?).and_return(false)
+ allow(controller).to receive(:current_user).and_return(user)
+ stub_experiment(test_experiment: true)
+ end
+
+ subject(:record_conversion_event) do
+ controller.record_experiment_conversion_event(:test_experiment)
+ end
+
+ it 'records the conversion event for the experiment & user' do
+ expect(::Experiment).to receive(:record_conversion_event).with(:test_experiment, user)
+ record_conversion_event
+ end
+
+ shared_examples 'does not record the conversion event' do
+ it 'does not record the conversion event' do
+ expect(::Experiment).not_to receive(:record_conversion_event)
+ record_conversion_event
+ end
+ end
+
+ context 'when DNT is enabled' do
+ before do
+ allow(controller).to receive(:dnt_enabled?).and_return(true)
+ end
+
+ include_examples 'does not record the conversion event'
+ end
+
+ context 'when there is no current user' do
+ before do
+ allow(controller).to receive(:current_user).and_return(nil)
+ end
+
+ include_examples 'does not record the conversion event'
+ end
+
+ context 'when the experiment is not enabled' do
+ before do
+ stub_experiment(test_experiment: false)
+ end
+
+ include_examples 'does not record the conversion event'
+ end
+ end
+
describe '#experiment_tracking_category_and_group' do
let_it_be(:experiment_key) { :test_something }
@@ -430,7 +540,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
it 'returns a string with the experiment tracking category & group joined with a ":"' do
expect(controller).to receive(:tracking_category).with(experiment_key).and_return('Experiment::Category')
- expect(controller).to receive(:tracking_group).with(experiment_key, '_group').and_return('experimental_group')
+ expect(controller).to receive(:tracking_group).with(experiment_key, '_group', subject: nil).and_return('experimental_group')
expect(subject).to eq('Experiment::Category:experimental_group')
end
diff --git a/spec/lib/gitlab/experimentation/experiment_spec.rb b/spec/lib/gitlab/experimentation/experiment_spec.rb
new file mode 100644
index 00000000000..7b1d1763010
--- /dev/null
+++ b/spec/lib/gitlab/experimentation/experiment_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Experimentation::Experiment do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:percentage) { 50 }
+ let(:params) do
+ {
+ tracking_category: 'Category1',
+ use_backwards_compatible_subject_index: true
+ }
+ end
+
+ before do
+ feature = double('FeatureFlag', percentage_of_time_value: percentage )
+ expect(Feature).to receive(:get).with(:experiment_key_experiment_percentage).and_return(feature)
+ end
+
+ subject(:experiment) { described_class.new(:experiment_key, **params) }
+
+ describe '#active?' do
+ before do
+ allow(Gitlab).to receive(:dev_env_or_com?).and_return(on_gitlab_com)
+ end
+
+ subject { experiment.active? }
+
+ where(:on_gitlab_com, :percentage, :is_active) do
+ true | 0 | false
+ true | 10 | true
+ false | 0 | false
+ false | 10 | false
+ end
+
+ with_them do
+ it { is_expected.to eq(is_active) }
+ end
+ end
+
+ describe '#enabled_for_index?' do
+ subject { experiment.enabled_for_index?(index) }
+
+ where(:index, :percentage, :is_enabled) do
+ 50 | 40 | false
+ 40 | 50 | true
+ nil | 50 | false
+ end
+
+ with_them do
+ it { is_expected.to eq(is_enabled) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
index ebf98a0151f..a68c050d829 100644
--- a/spec/lib/gitlab/experimentation_spec.rb
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -13,11 +13,8 @@ RSpec.describe Gitlab::Experimentation::EXPERIMENTS do
:invite_members_version_a,
:invite_members_version_b,
:invite_members_empty_group_version_a,
- :new_create_project_ui,
:contact_sales_btn_in_app,
:customize_homepage,
- :invite_email,
- :invitation_reminders,
:group_only_trials,
:default_to_issues_board
]
@@ -29,127 +26,150 @@ RSpec.describe Gitlab::Experimentation::EXPERIMENTS do
end
end
-RSpec.describe Gitlab::Experimentation, :snowplow do
+RSpec.describe Gitlab::Experimentation do
before do
stub_const('Gitlab::Experimentation::EXPERIMENTS', {
backwards_compatible_test_experiment: {
- environment: environment,
tracking_category: 'Team',
use_backwards_compatible_subject_index: true
},
test_experiment: {
- environment: environment,
tracking_category: 'Team'
}
})
Feature.enable_percentage_of_time(:backwards_compatible_test_experiment_experiment_percentage, enabled_percentage)
Feature.enable_percentage_of_time(:test_experiment_experiment_percentage, enabled_percentage)
+ allow(Gitlab).to receive(:com?).and_return(true)
end
- let(:environment) { Rails.env.test? }
let(:enabled_percentage) { 10 }
- describe '.enabled?' do
- subject { described_class.enabled?(:test_experiment) }
+ describe '.get_experiment' do
+ subject { described_class.get_experiment(:test_experiment) }
- context 'feature toggle is enabled, we are on the right environment and we are selected' do
- it { is_expected.to be_truthy }
+ context 'returns experiment' do
+ it { is_expected.to be_instance_of(Gitlab::Experimentation::Experiment) }
+ end
+
+ context 'experiment is not defined' do
+ subject { described_class.get_experiment(:missing_experiment) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '.active?' do
+ subject { described_class.active?(:test_experiment) }
+
+ context 'feature toggle is enabled' do
+ it { is_expected.to eq(true) }
end
describe 'experiment is not defined' do
it 'returns false' do
- expect(described_class.enabled?(:missing_experiment)).to be_falsey
+ expect(described_class.active?(:missing_experiment)).to eq(false)
end
end
describe 'experiment is disabled' do
let(:enabled_percentage) { 0 }
- it { is_expected.to be_falsey }
+ it { is_expected.to eq(false) }
end
+ end
- describe 'we are on the wrong environment' do
- let(:environment) { ::Gitlab.com? }
+ describe '.in_experiment_group?' do
+ context 'with new index calculation' do
+ let(:enabled_percentage) { 50 }
+ let(:experiment_subject) { 'z' } # Zlib.crc32('test_experimentz') % 100 = 33
- it { is_expected.to be_falsey }
+ subject { described_class.in_experiment_group?(:test_experiment, subject: experiment_subject) }
- it 'ensures the typically less expensive environment is checked before the more expensive call to database for Feature' do
- expect_next_instance_of(described_class::Experiment) do |experiment|
- expect(experiment).not_to receive(:enabled?)
+ context 'when experiment is active' do
+ context 'when subject is part of the experiment' do
+ it { is_expected.to eq(true) }
end
- subject
- end
- end
- end
+ context 'when subject is not part of the experiment' do
+ let(:experiment_subject) { 'a' } # Zlib.crc32('test_experimenta') % 100 = 61
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when subject has a global_id' do
+ let(:experiment_subject) { double(:subject, to_global_id: 'z') }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when subject is nil' do
+ let(:experiment_subject) { nil }
- describe '.enabled_for_value?' do
- subject { described_class.enabled_for_value?(:test_experiment, experimentation_subject_index) }
+ it { is_expected.to eq(false) }
+ end
- let(:experimentation_subject_index) { 9 }
+ context 'when subject is an empty string' do
+ let(:experiment_subject) { '' }
- context 'experiment is disabled' do
- before do
- allow(described_class).to receive(:enabled?).and_return(false)
+ it { is_expected.to eq(false) }
+ end
end
- it { is_expected.to be_falsey }
- end
+ context 'when experiment is not active' do
+ before do
+ allow(described_class).to receive(:active?).and_return(false)
+ end
- context 'experiment is enabled' do
- before do
- allow(described_class).to receive(:enabled?).and_return(true)
+ it { is_expected.to eq(false) }
end
+ end
- it { is_expected.to be_truthy }
+ context 'with backwards compatible index calculation' do
+ let(:experiment_subject) { 'abcd' } # Digest::SHA1.hexdigest('abcd').hex % 100 = 7
- describe 'experimentation_subject_index' do
- context 'experimentation_subject_index is not set' do
- let(:experimentation_subject_index) { nil }
+ subject { described_class.in_experiment_group?(:backwards_compatible_test_experiment, subject: experiment_subject) }
- it { is_expected.to be_falsey }
+ context 'when experiment is active' do
+ before do
+ allow(described_class).to receive(:active?).and_return(true)
end
- context 'experimentation_subject_index is an empty string' do
- let(:experimentation_subject_index) { '' }
-
- it { is_expected.to be_falsey }
+ context 'when subject is part of the experiment' do
+ it { is_expected.to eq(true) }
end
- context 'experimentation_subject_index outside enabled ratio' do
- let(:experimentation_subject_index) { 11 }
+ context 'when subject is not part of the experiment' do
+ let(:experiment_subject) { 'abc' } # Digest::SHA1.hexdigest('abc').hex % 100 = 17
- it { is_expected.to be_falsey }
+ it { is_expected.to eq(false) }
end
- end
- end
- end
- describe '.enabled_for_attribute?' do
- subject { described_class.enabled_for_attribute?(:test_experiment, attribute) }
+ context 'when subject has a global_id' do
+ let(:experiment_subject) { double(:subject, to_global_id: 'abcd') }
- let(:attribute) { 'abcd' } # Digest::SHA1.hexdigest('abcd').hex % 100 = 7
+ it { is_expected.to eq(true) }
+ end
- context 'experiment is disabled' do
- before do
- allow(described_class).to receive(:enabled?).and_return(false)
- end
+ context 'when subject is nil' do
+ let(:experiment_subject) { nil }
- it { is_expected.to be false }
- end
+ it { is_expected.to eq(false) }
+ end
- context 'experiment is enabled' do
- before do
- allow(described_class).to receive(:enabled?).and_return(true)
- end
+ context 'when subject is an empty string' do
+ let(:experiment_subject) { '' }
- it { is_expected.to be true }
+ it { is_expected.to eq(false) }
+ end
+ end
- context 'outside enabled ratio' do
- let(:attribute) { 'abc' } # Digest::SHA1.hexdigest('abc').hex % 100 = 17
+ context 'when experiment is not active' do
+ before do
+ allow(described_class).to receive(:active?).and_return(false)
+ end
- it { is_expected.to be false }
+ it { is_expected.to eq(false) }
end
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 6dfa791f70b..c917945499c 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -929,7 +929,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
context 'with max_count' do
- it 'returns the number of commits with path ' do
+ it 'returns the number of commits with path' do
options = { ref: 'master', max_count: 5 }
expect(repository.count_commits(options)).to eq(5)
@@ -937,7 +937,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
context 'with path' do
- it 'returns the number of commits with path ' do
+ it 'returns the number of commits with path' do
options = { ref: 'master', path: 'encoding' }
expect(repository.count_commits(options)).to eq(2)
@@ -965,7 +965,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
context 'with max_count' do
- it 'returns the number of commits with path ' do
+ it 'returns the number of commits with path' do
options = { from: 'fix-mode', to: 'fix-blob-path', left_right: true, max_count: 1 }
expect(repository.count_commits(options)).to eq([1, 1])
@@ -1185,6 +1185,66 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#find_changed_paths' do
+ let(:commit_1) { 'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6' }
+ let(:commit_2) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
+ let(:commit_3) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' }
+ let(:commit_1_files) do
+ [
+ OpenStruct.new(status: :ADDED, path: "files/executables/ls"),
+ OpenStruct.new(status: :ADDED, path: "files/executables/touch"),
+ OpenStruct.new(status: :ADDED, path: "files/links/regex.rb"),
+ OpenStruct.new(status: :ADDED, path: "files/links/ruby-style-guide.md"),
+ OpenStruct.new(status: :ADDED, path: "files/links/touch"),
+ OpenStruct.new(status: :MODIFIED, path: ".gitmodules"),
+ OpenStruct.new(status: :ADDED, path: "deeper/nested/six"),
+ OpenStruct.new(status: :ADDED, path: "nested/six")
+ ]
+ end
+
+ let(:commit_2_files) do
+ [OpenStruct.new(status: :ADDED, path: "bin/executable")]
+ end
+
+ let(:commit_3_files) do
+ [
+ OpenStruct.new(status: :MODIFIED, path: ".gitmodules"),
+ OpenStruct.new(status: :ADDED, path: "gitlab-shell")
+ ]
+ end
+
+ it 'returns a list of paths' do
+ collection = repository.find_changed_paths([commit_1, commit_2, commit_3])
+
+ expect(collection).to be_a(Enumerable)
+ expect(collection.to_a).to eq(commit_1_files + commit_2_files + commit_3_files)
+ end
+
+ it 'returns no paths when SHAs are invalid' do
+ collection = repository.find_changed_paths(['invalid', commit_1])
+
+ expect(collection).to be_a(Enumerable)
+ expect(collection.to_a).to be_empty
+ end
+
+ it 'returns a list of paths even when containing a blank ref' do
+ collection = repository.find_changed_paths([nil, commit_1])
+
+ expect(collection).to be_a(Enumerable)
+ expect(collection.to_a).to eq(commit_1_files)
+ end
+
+ it 'returns no paths when the commits are nil' do
+ expect_any_instance_of(Gitlab::GitalyClient::CommitService)
+ .not_to receive(:find_changed_paths)
+
+ collection = repository.find_changed_paths([nil, nil])
+
+ expect(collection).to be_a(Enumerable)
+ expect(collection.to_a).to be_empty
+ end
+ end
+
describe "#ls_files" do
let(:master_file_paths) { repository.ls_files("master") }
let(:utf8_file_paths) { repository.ls_files("ls-files-utf8") }
diff --git a/spec/lib/gitlab/git_access_project_spec.rb b/spec/lib/gitlab/git_access_project_spec.rb
index f80915b2be9..953b74cf1a9 100644
--- a/spec/lib/gitlab/git_access_project_spec.rb
+++ b/spec/lib/gitlab/git_access_project_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe Gitlab::GitAccessProject do
let(:actor) { user }
let(:project_path) { project.path }
let(:namespace_path) { project&.namespace&.path }
+ let(:repository_path) { "#{namespace_path}/#{project_path}.git" }
let(:protocol) { 'ssh' }
let(:authentication_abilities) { %i[read_project download_code push_code] }
let(:changes) { Gitlab::GitAccess::ANY }
@@ -17,7 +18,7 @@ RSpec.describe Gitlab::GitAccessProject do
let(:access) do
described_class.new(actor, container, protocol,
authentication_abilities: authentication_abilities,
- repository_path: project_path, namespace_path: namespace_path)
+ repository_path: repository_path)
end
describe '#check_namespace!' do
@@ -103,6 +104,20 @@ RSpec.describe Gitlab::GitAccessProject do
end
end
+ context 'when namespace is blank' do
+ let(:repository_path) { 'project.git' }
+
+ it_behaves_like 'no project is created' do
+ let(:raise_specific_error) { raise_namespace_not_found }
+ end
+ end
+
+ context 'when namespace does not exist' do
+ let(:namespace_path) { 'unknown' }
+
+ it_behaves_like 'no project is created'
+ end
+
context 'when user cannot create project in namespace' do
let(:user2) { create(:user) }
let(:namespace_path) { user2.namespace.path }
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 21607edbc32..780f4329bcc 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -10,8 +10,7 @@ RSpec.describe Gitlab::GitAccess do
let(:actor) { user }
let(:project) { create(:project, :repository) }
- let(:project_path) { project&.path }
- let(:namespace_path) { project&.namespace&.path }
+ let(:repository_path) { "#{project.full_path}.git" }
let(:protocol) { 'ssh' }
let(:authentication_abilities) { %i[read_project download_code push_code] }
let(:redirected_path) { nil }
@@ -210,10 +209,9 @@ RSpec.describe Gitlab::GitAccess do
end
end
- context 'when the project is nil' do
+ context 'when the project does not exist' do
let(:project) { nil }
- let(:project_path) { "new-project" }
- let(:namespace_path) { user.namespace.path }
+ let(:repository_path) { "#{user.namespace.path}/new-project.git" }
it 'blocks push and pull with "not found"' do
aggregate_failures do
@@ -389,6 +387,108 @@ RSpec.describe Gitlab::GitAccess do
end
end
+ describe '#check_otp_session!' do
+ let_it_be(:user) { create(:user, :two_factor_via_otp)}
+ let_it_be(:key) { create(:key, user: user) }
+ let_it_be(:actor) { key }
+
+ before do
+ project.add_developer(user)
+ stub_feature_flags(two_factor_for_cli: true)
+ end
+
+ context 'with an OTP session', :clean_gitlab_redis_shared_state do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set("#{Gitlab::Auth::Otp::SessionEnforcer::OTP_SESSIONS_NAMESPACE}:#{key.id}", true)
+ end
+ end
+
+ it 'allows push and pull access' do
+ aggregate_failures do
+ expect { push_access_check }.not_to raise_error
+ expect { pull_access_check }.not_to raise_error
+ end
+ end
+ end
+
+ context 'without OTP session' do
+ it 'does not allow push or pull access' do
+ user = 'jane.doe'
+ host = 'fridge.ssh'
+ port = 42
+
+ stub_config(
+ gitlab_shell: {
+ ssh_user: user,
+ ssh_host: host,
+ ssh_port: port
+ }
+ )
+
+ error_message = "OTP verification is required to access the repository.\n\n"\
+ " Use: ssh #{user}@#{host} -p #{port} 2fa_verify"
+
+ aggregate_failures do
+ expect { push_access_check }.to raise_forbidden(error_message)
+ expect { pull_access_check }.to raise_forbidden(error_message)
+ end
+ end
+
+ context 'when protocol is HTTP' do
+ let(:protocol) { 'http' }
+
+ it 'allows push and pull access' do
+ aggregate_failures do
+ expect { push_access_check }.not_to raise_error
+ expect { pull_access_check }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when actor is not an SSH key' do
+ let(:deploy_key) { create(:deploy_key, user: user) }
+ let(:actor) { deploy_key }
+
+ before do
+ deploy_key.deploy_keys_projects.create(project: project, can_push: true)
+ end
+
+ it 'allows push and pull access' do
+ aggregate_failures do
+ expect { push_access_check }.not_to raise_error
+ expect { pull_access_check }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when 2FA is not enabled for the user' do
+ let(:user) { create(:user)}
+ let(:actor) { create(:key, user: user) }
+
+ it 'allows push and pull access' do
+ aggregate_failures do
+ expect { push_access_check }.not_to raise_error
+ expect { pull_access_check }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(two_factor_for_cli: false)
+ end
+
+ it 'allows push and pull access' do
+ aggregate_failures do
+ expect { push_access_check }.not_to raise_error
+ expect { pull_access_check }.not_to raise_error
+ end
+ end
+ end
+ end
+ end
+
describe '#check_db_accessibility!' do
context 'when in a read-only GitLab instance' do
before do
@@ -452,9 +552,8 @@ RSpec.describe Gitlab::GitAccess do
context 'when project is public' do
let(:public_project) { create(:project, :public, :repository) }
- let(:project_path) { public_project.path }
- let(:namespace_path) { public_project.namespace.path }
- let(:access) { access_class.new(nil, public_project, 'web', authentication_abilities: [:download_code], repository_path: project_path, namespace_path: namespace_path) }
+ let(:repository_path) { "#{public_project.full_path}.git" }
+ let(:access) { access_class.new(nil, public_project, 'web', authentication_abilities: [:download_code], repository_path: repository_path) }
context 'when repository is enabled' do
it 'give access to download code' do
@@ -1169,7 +1268,7 @@ RSpec.describe Gitlab::GitAccess do
def access
access_class.new(actor, project, protocol,
authentication_abilities: authentication_abilities,
- namespace_path: namespace_path, repository_path: project_path,
+ repository_path: repository_path,
redirected_path: redirected_path, auth_result_type: auth_result_type)
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index b09bd9dff1b..157c2393ce1 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -145,6 +145,31 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
end
end
+ describe '#find_changed_paths' do
+ let(:commits) { %w[1a0b36b3cdad1d2ee32457c102a8c0b7056fa863 cfe32cf61b73a0d5e9f13e774abde7ff789b1660] }
+
+ it 'sends an RPC request and returns the stats' do
+ request = Gitaly::FindChangedPathsRequest.new(repository: repository_message,
+ commits: commits)
+
+ changed_paths_response = Gitaly::FindChangedPathsResponse.new(
+ paths: [{
+ path: "app/assets/javascripts/boards/components/project_select.vue",
+ status: :MODIFIED
+ }])
+
+ expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:find_changed_paths)
+ .with(request, kind_of(Hash)).and_return([changed_paths_response])
+
+ returned_value = described_class.new(repository).find_changed_paths(commits)
+
+ mapped_returned_value = returned_value.map(&:to_h)
+ mapped_expected_value = changed_paths_response.paths.map(&:to_h)
+
+ expect(mapped_returned_value).to eq(mapped_expected_value)
+ end
+ end
+
describe '#tree_entries' do
let(:path) { '/' }
@@ -357,7 +382,7 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
end
it 'sends an RPC request with the correct payload' do
- expect(client.commits_by_message(query, options)).to match_array(wrap_commits(commits))
+ expect(client.commits_by_message(query, **options)).to match_array(wrap_commits(commits))
end
end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index 16dd2bbee6d..7fcb11c4dfd 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -53,7 +53,7 @@ RSpec.describe Gitlab::GitalyClient do
describe '.filesystem_id_from_disk' do
it 'catches errors' do
[Errno::ENOENT, Errno::EACCES, JSON::ParserError].each do |error|
- allow(File).to receive(:read).with(described_class.storage_metadata_file_path('default')).and_raise(error)
+ stub_file_read(described_class.storage_metadata_file_path('default'), error: error)
expect(described_class.filesystem_id_from_disk('default')).to be_nil
end
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index bc734644d29..4000e0b2611 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -28,6 +28,17 @@ RSpec.describe Gitlab::GithubImport::Client do
end
end
+ describe '#pull_request_reviews' do
+ it 'returns the pull request reviews' do
+ client = described_class.new('foo')
+
+ expect(client.octokit).to receive(:pull_request_reviews).with('foo/bar', 999)
+ expect(client).to receive(:with_rate_limit).and_yield
+
+ client.pull_request_reviews('foo/bar', 999)
+ end
+ end
+
describe '#repository' do
it 'returns the details of a repository' do
client = described_class.new('foo')
@@ -39,6 +50,17 @@ RSpec.describe Gitlab::GithubImport::Client do
end
end
+ describe '#pull_request' do
+ it 'returns the details of a pull_request' do
+ client = described_class.new('foo')
+
+ expect(client.octokit).to receive(:pull_request).with('foo/bar', 999)
+ expect(client).to receive(:with_rate_limit).and_yield
+
+ client.pull_request('foo/bar', 999)
+ end
+ end
+
describe '#labels' do
it 'returns the labels' do
client = described_class.new('foo')
@@ -478,7 +500,7 @@ RSpec.describe Gitlab::GithubImport::Client do
it 'searches for repositories based on name' do
expected_search_query = 'test in:name is:public,private user:user repo:repo1 repo:repo2 org:org1 org:org2'
- expect(client).to receive(:each_page).with(:search_repositories, expected_search_query)
+ expect(client.octokit).to receive(:search_repositories).with(expected_search_query, {})
client.search_repos_by_name('test')
end
diff --git a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
index 6188ba8ec3f..8ee534734f0 100644
--- a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
@@ -49,6 +49,57 @@ RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
importer.execute
end
end
+
+ context 'when LFS list download fails' do
+ it 'rescues and logs the known exceptions' do
+ exception = StandardError.new('Invalid Project URL')
+ importer = described_class.new(project, client, parallel: false)
+
+ expect_next_instance_of(Projects::LfsPointers::LfsObjectDownloadListService) do |service|
+ expect(service)
+ .to receive(:execute)
+ .and_raise(exception)
+ end
+
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:error)
+ .with(
+ message: 'importer failed',
+ import_source: :github,
+ project_id: project.id,
+ parallel: false,
+ importer: 'Gitlab::GithubImport::Importer::LfsObjectImporter',
+ 'error.message': 'Invalid Project URL'
+ )
+ end
+
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_exception)
+ .with(
+ exception,
+ import_source: :github,
+ parallel: false,
+ project_id: project.id,
+ importer: 'Gitlab::GithubImport::Importer::LfsObjectImporter'
+ ).and_call_original
+
+ importer.execute
+ end
+
+ it 'raises and logs the unknown exceptions' do
+ exception = Exception.new('Really bad news')
+ importer = described_class.new(project, client, parallel: false)
+
+ expect_next_instance_of(Projects::LfsPointers::LfsObjectDownloadListService) do |service|
+ expect(service)
+ .to receive(:execute)
+ .and_raise(exception)
+ end
+
+ expect { importer.execute }.to raise_error(exception)
+ end
+ end
end
describe '#sequential_import' do
@@ -56,18 +107,16 @@ RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
importer = described_class.new(project, client, parallel: false)
lfs_object_importer = double(:lfs_object_importer)
- allow(importer)
- .to receive(:each_object_to_import)
- .and_yield(lfs_download_object)
+ expect_next_instance_of(Projects::LfsPointers::LfsObjectDownloadListService) do |service|
+ expect(service).to receive(:execute).and_return([lfs_download_object])
+ end
expect(Gitlab::GithubImport::Importer::LfsObjectImporter)
- .to receive(:new)
- .with(
+ .to receive(:new).with(
an_instance_of(Gitlab::GithubImport::Representation::LfsObject),
project,
client
- )
- .and_return(lfs_object_importer)
+ ).and_return(lfs_object_importer)
expect(lfs_object_importer).to receive(:execute)
@@ -79,9 +128,9 @@ RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
it 'imports each lfs object in parallel' do
importer = described_class.new(project, client)
- allow(importer)
- .to receive(:each_object_to_import)
- .and_yield(lfs_download_object)
+ expect_next_instance_of(Projects::LfsPointers::LfsObjectDownloadListService) do |service|
+ expect(service).to receive(:execute).and_return([lfs_download_object])
+ end
expect(Gitlab::GithubImport::ImportLfsObjectWorker)
.to receive(:perform_async)
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index 46850618945..c7388314253 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -43,7 +43,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitla
describe '#execute' do
it 'imports the pull request' do
- mr = double(:merge_request, id: 10)
+ mr = double(:merge_request, id: 10, merged?: false)
expect(importer)
.to receive(:create_merge_request)
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb
new file mode 100644
index 00000000000..2999dc5bb41
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :clean_gitlab_redis_cache do
+ let_it_be(:merge_request) { create(:merged_merge_request) }
+ let(:project) { merge_request.project }
+ let(:created_at) { Time.new(2017, 1, 1, 12, 00).utc }
+ let(:client_double) { double(user: double(id: 999, login: 'merger', email: 'merger@email.com')) }
+
+ let(:pull_request) do
+ instance_double(
+ Gitlab::GithubImport::Representation::PullRequest,
+ iid: merge_request.iid,
+ created_at: created_at,
+ merged_by: double(id: 999, login: 'merger')
+ )
+ end
+
+ subject { described_class.new(pull_request, project, client_double) }
+
+ it 'assigns the merged by user when mapped' do
+ merge_user = create(:user, email: 'merger@email.com')
+
+ subject.execute
+
+ expect(merge_request.metrics.reload.merged_by).to eq(merge_user)
+ end
+
+ it 'adds a note referencing the merger user when the user cannot be mapped' do
+ expect { subject.execute }
+ .to change(Note, :count).by(1)
+ .and not_change(merge_request, :updated_at)
+
+ last_note = merge_request.notes.last
+
+ expect(last_note.note).to eq("*Merged by: merger*")
+ expect(last_note.created_at).to eq(created_at)
+ expect(last_note.author).to eq(project.creator)
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb
new file mode 100644
index 00000000000..b2f993ac47c
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb
@@ -0,0 +1,202 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean_gitlab_redis_cache do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:client_double) { double(user: double(id: 999, login: 'author', email: 'author@email.com')) }
+ let(:submitted_at) { Time.new(2017, 1, 1, 12, 00).utc }
+
+ subject { described_class.new(review, project, client_double) }
+
+ context 'when the review author can be mapped to a gitlab user' do
+ let_it_be(:author) { create(:user, email: 'author@email.com') }
+
+ context 'when the review has no note text' do
+ context 'when the review is "APPROVED"' do
+ let(:review) { create_review(type: 'APPROVED', note: '') }
+
+ it 'creates a note for the review' do
+ expect { subject.execute }.to change(Note, :count)
+
+ last_note = merge_request.notes.last
+ expect(last_note.note).to eq('approved this merge request')
+ expect(last_note.author).to eq(author)
+ expect(last_note.created_at).to eq(submitted_at)
+ expect(last_note.system_note_metadata.action).to eq('approved')
+
+ expect(merge_request.approved_by_users.reload).to include(author)
+ expect(merge_request.approvals.last.created_at).to eq(submitted_at)
+ end
+ end
+
+ context 'when the review is "COMMENTED"' do
+ let(:review) { create_review(type: 'COMMENTED', note: '') }
+
+ it 'creates a note for the review' do
+ expect { subject.execute }.not_to change(Note, :count)
+ end
+ end
+
+ context 'when the review is "CHANGES_REQUESTED"' do
+ let(:review) { create_review(type: 'CHANGES_REQUESTED', note: '') }
+
+ it 'creates a note for the review' do
+ expect { subject.execute }.not_to change(Note, :count)
+ end
+ end
+ end
+
+ context 'when the review has a note text' do
+ context 'when the review is "APPROVED"' do
+ let(:review) { create_review(type: 'APPROVED') }
+
+ it 'creates a note for the review' do
+ expect { subject.execute }
+ .to change(Note, :count).by(2)
+ .and change(Approval, :count).by(1)
+
+ note = merge_request.notes.where(system: false).last
+ expect(note.note).to eq("**Review:** Approved\n\nnote")
+ expect(note.author).to eq(author)
+ expect(note.created_at).to eq(submitted_at)
+
+ system_note = merge_request.notes.where(system: true).last
+ expect(system_note.note).to eq('approved this merge request')
+ expect(system_note.author).to eq(author)
+ expect(system_note.created_at).to eq(submitted_at)
+ expect(system_note.system_note_metadata.action).to eq('approved')
+
+ expect(merge_request.approved_by_users.reload).to include(author)
+ expect(merge_request.approvals.last.created_at).to eq(submitted_at)
+ end
+ end
+
+ context 'when the review is "COMMENTED"' do
+ let(:review) { create_review(type: 'COMMENTED') }
+
+ it 'creates a note for the review' do
+ expect { subject.execute }
+ .to change(Note, :count).by(1)
+ .and not_change(Approval, :count)
+
+ last_note = merge_request.notes.last
+
+ expect(last_note.note).to eq("**Review:** Commented\n\nnote")
+ expect(last_note.author).to eq(author)
+ expect(last_note.created_at).to eq(submitted_at)
+ end
+ end
+
+ context 'when the review is "CHANGES_REQUESTED"' do
+ let(:review) { create_review(type: 'CHANGES_REQUESTED') }
+
+ it 'creates a note for the review' do
+ expect { subject.execute }
+ .to change(Note, :count).by(1)
+ .and not_change(Approval, :count)
+
+ last_note = merge_request.notes.last
+
+ expect(last_note.note).to eq("**Review:** Changes requested\n\nnote")
+ expect(last_note.author).to eq(author)
+ expect(last_note.created_at).to eq(submitted_at)
+ end
+ end
+ end
+ end
+
+ context 'when the review author cannot be mapped to a gitlab user' do
+ context 'when the review has no note text' do
+ context 'when the review is "APPROVED"' do
+ let(:review) { create_review(type: 'APPROVED', note: '') }
+
+ it 'creates a note for the review with *Approved by by<author>*' do
+ expect { subject.execute }
+ .to change(Note, :count).by(1)
+
+ last_note = merge_request.notes.last
+ expect(last_note.note).to eq("*Created by author*\n\n**Review:** Approved")
+ expect(last_note.author).to eq(project.creator)
+ expect(last_note.created_at).to eq(submitted_at)
+ end
+ end
+
+ context 'when the review is "COMMENTED"' do
+ let(:review) { create_review(type: 'COMMENTED', note: '') }
+
+ it 'creates a note for the review with *Commented by<author>*' do
+ expect { subject.execute }.not_to change(Note, :count)
+ end
+ end
+
+ context 'when the review is "CHANGES_REQUESTED"' do
+ let(:review) { create_review(type: 'CHANGES_REQUESTED', note: '') }
+
+ it 'creates a note for the review with *Changes requested by <author>*' do
+ expect { subject.execute }.not_to change(Note, :count)
+ end
+ end
+ end
+
+ context 'when the review has a note text' do
+ context 'when the review is "APPROVED"' do
+ let(:review) { create_review(type: 'APPROVED') }
+
+ it 'creates a note for the review with *Approved by by<author>*' do
+ expect { subject.execute }
+ .to change(Note, :count).by(1)
+
+ last_note = merge_request.notes.last
+
+ expect(last_note.note).to eq("*Created by author*\n\n**Review:** Approved\n\nnote")
+ expect(last_note.author).to eq(project.creator)
+ expect(last_note.created_at).to eq(submitted_at)
+ end
+ end
+
+ context 'when the review is "COMMENTED"' do
+ let(:review) { create_review(type: 'COMMENTED') }
+
+ it 'creates a note for the review with *Commented by<author>*' do
+ expect { subject.execute }
+ .to change(Note, :count).by(1)
+
+ last_note = merge_request.notes.last
+
+ expect(last_note.note).to eq("*Created by author*\n\n**Review:** Commented\n\nnote")
+ expect(last_note.author).to eq(project.creator)
+ expect(last_note.created_at).to eq(submitted_at)
+ end
+ end
+
+ context 'when the review is "CHANGES_REQUESTED"' do
+ let(:review) { create_review(type: 'CHANGES_REQUESTED') }
+
+ it 'creates a note for the review with *Changes requested by <author>*' do
+ expect { subject.execute }
+ .to change(Note, :count).by(1)
+
+ last_note = merge_request.notes.last
+
+ expect(last_note.note).to eq("*Created by author*\n\n**Review:** Changes requested\n\nnote")
+ expect(last_note.author).to eq(project.creator)
+ expect(last_note.created_at).to eq(submitted_at)
+ end
+ end
+ end
+ end
+
+ def create_review(type:, note: 'note')
+ Gitlab::GithubImport::Representation::PullRequestReview.from_json_hash(
+ merge_request_id: merge_request.id,
+ review_type: type,
+ note: note,
+ submitted_at: submitted_at.to_s,
+ author: { id: 999, login: 'author' }
+ )
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
index 0835c6155b9..8a7867f3841 100644
--- a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
@@ -29,6 +29,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsImporter do
milestone: double(:milestone, number: 4),
user: double(:user, id: 4, login: 'alice'),
assignee: double(:user, id: 4, login: 'alice'),
+ merged_by: double(:user, id: 4, login: 'alice'),
created_at: 1.second.ago,
updated_at: 1.second.ago,
merged_at: 1.second.ago
diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_merged_by_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_merged_by_importer_spec.rb
new file mode 100644
index 00000000000..b859cc727a6
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/pull_requests_merged_by_importer_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do
+ let(:client) { double }
+ let(:project) { create(:project, import_source: 'http://somegithub.com') }
+
+ subject { described_class.new(project, client) }
+
+ it { is_expected.to include_module(Gitlab::GithubImport::ParallelScheduling) }
+
+ describe '#representation_class' do
+ it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::PullRequest) }
+ end
+
+ describe '#importer_class' do
+ it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequestMergedByImporter) }
+ end
+
+ describe '#collection_method' do
+ it { expect(subject.collection_method).to eq(:pull_requests_merged_by) }
+ end
+
+ describe '#id_for_already_imported_cache' do
+ it { expect(subject.id_for_already_imported_cache(double(number: 1))).to eq(1) }
+ end
+
+ describe '#each_object_to_import' do
+ it 'fetchs the merged pull requests data' do
+ pull_request = double
+ create(
+ :merged_merge_request,
+ iid: 999,
+ source_project: project,
+ target_project: project
+ )
+
+ allow(client)
+ .to receive(:pull_request)
+ .with('http://somegithub.com', 999)
+ .and_return(pull_request)
+
+ expect { |b| subject.each_object_to_import(&b) }.to yield_with_args(pull_request)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_reviews_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_reviews_importer_spec.rb
new file mode 100644
index 00000000000..5e2302f9662
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/pull_requests_reviews_importer_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::PullRequestsReviewsImporter do
+ let(:client) { double }
+ let(:project) { create(:project, import_source: 'github/repo') }
+
+ subject { described_class.new(project, client) }
+
+ it { is_expected.to include_module(Gitlab::GithubImport::ParallelScheduling) }
+
+ describe '#representation_class' do
+ it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::PullRequestReview) }
+ end
+
+ describe '#importer_class' do
+ it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequestReviewImporter) }
+ end
+
+ describe '#collection_method' do
+ it { expect(subject.collection_method).to eq(:pull_request_reviews) }
+ end
+
+ describe '#id_for_already_imported_cache' do
+ it { expect(subject.id_for_already_imported_cache(double(github_id: 1))).to eq(1) }
+ end
+
+ describe '#each_object_to_import' do
+ it 'fetchs the merged pull requests data' do
+ merge_request = create(:merge_request, source_project: project)
+ review = double
+
+ expect(review)
+ .to receive(:merge_request_id=)
+ .with(merge_request.id)
+
+ allow(client)
+ .to receive(:pull_request_reviews)
+ .with('github/repo', merge_request.iid)
+ .and_return([review])
+
+ expect { |b| subject.each_object_to_import(&b) }.to yield_with_args(review)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
index 578743be96b..1e31cd2f007 100644
--- a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
@@ -7,6 +7,10 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do
Class.new do
include(Gitlab::GithubImport::ParallelScheduling)
+ def importer_class
+ Class
+ end
+
def collection_method
:issues
end
@@ -63,6 +67,82 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do
importer.execute
end
+
+ it 'logs the the process' do
+ importer = importer_class.new(project, client, parallel: false)
+
+ expect(importer)
+ .to receive(:sequential_import)
+ .and_return([])
+
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'starting importer',
+ import_source: :github,
+ parallel: false,
+ project_id: project.id,
+ importer: 'Class'
+ )
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'importer finished',
+ import_source: :github,
+ parallel: false,
+ project_id: project.id,
+ importer: 'Class'
+ )
+ end
+
+ importer.execute
+ end
+
+ it 'logs the error when it fails' do
+ exception = StandardError.new('some error')
+
+ importer = importer_class.new(project, client, parallel: false)
+
+ expect(importer)
+ .to receive(:sequential_import)
+ .and_raise(exception)
+
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'starting importer',
+ import_source: :github,
+ parallel: false,
+ project_id: project.id,
+ importer: 'Class'
+ )
+ expect(logger)
+ .to receive(:error)
+ .with(
+ message: 'importer failed',
+ import_source: :github,
+ project_id: project.id,
+ parallel: false,
+ importer: 'Class',
+ 'error.message': 'some error'
+ )
+ end
+
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_exception)
+ .with(
+ exception,
+ import_source: :github,
+ parallel: false,
+ project_id: project.id,
+ importer: 'Class'
+ )
+ .and_call_original
+
+ expect { importer.execute }.to raise_error(exception)
+ end
end
describe '#sequential_import' do
diff --git a/spec/lib/gitlab/github_import/representation/pull_request_review_spec.rb b/spec/lib/gitlab/github_import/representation/pull_request_review_spec.rb
new file mode 100644
index 00000000000..f9763455468
--- /dev/null
+++ b/spec/lib/gitlab/github_import/representation/pull_request_review_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Representation::PullRequestReview do
+ let(:submitted_at) { Time.new(2017, 1, 1, 12, 00).utc }
+
+ shared_examples 'a PullRequest review' do
+ it 'returns an instance of PullRequest' do
+ expect(review).to be_an_instance_of(described_class)
+ expect(review.author).to be_an_instance_of(Gitlab::GithubImport::Representation::User)
+ expect(review.author.id).to eq(4)
+ expect(review.author.login).to eq('alice')
+ expect(review.note).to eq('note')
+ expect(review.review_type).to eq('APPROVED')
+ expect(review.submitted_at).to eq(submitted_at)
+ expect(review.github_id).to eq(999)
+ expect(review.merge_request_id).to eq(42)
+ end
+ end
+
+ describe '.from_api_response' do
+ let(:response) do
+ double(
+ :response,
+ id: 999,
+ merge_request_id: 42,
+ body: 'note',
+ state: 'APPROVED',
+ user: double(:user, id: 4, login: 'alice'),
+ submitted_at: submitted_at
+ )
+ end
+
+ it_behaves_like 'a PullRequest review' do
+ let(:review) { described_class.from_api_response(response) }
+ end
+
+ it 'does not set the user if the response did not include a user' do
+ allow(response)
+ .to receive(:user)
+ .and_return(nil)
+
+ review = described_class.from_api_response(response)
+
+ expect(review.author).to be_nil
+ end
+ end
+
+ describe '.from_json_hash' do
+ let(:hash) do
+ {
+ 'github_id' => 999,
+ 'merge_request_id' => 42,
+ 'note' => 'note',
+ 'review_type' => 'APPROVED',
+ 'author' => { 'id' => 4, 'login' => 'alice' },
+ 'submitted_at' => submitted_at.to_s
+ }
+ end
+
+ it_behaves_like 'a PullRequest review' do
+ let(:review) { described_class.from_json_hash(hash) }
+ end
+
+ it 'does not set the user if the response did not include a user' do
+ review = described_class.from_json_hash(hash.except('author'))
+
+ expect(review.author).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/representation/pull_request_spec.rb b/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
index 370eac1d993..27a82951b01 100644
--- a/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
@@ -115,6 +115,7 @@ RSpec.describe Gitlab::GithubImport::Representation::PullRequest do
milestone: double(:milestone, number: 4),
user: double(:user, id: 4, login: 'alice'),
assignee: double(:user, id: 4, login: 'alice'),
+ merged_by: double(:user, id: 4, login: 'alice'),
created_at: created_at,
updated_at: updated_at,
merged_at: merged_at
diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb
deleted file mode 100644
index 402d2169432..00000000000
--- a/spec/lib/gitlab/google_code_import/client_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require "spec_helper"
-
-RSpec.describe Gitlab::GoogleCodeImport::Client do
- let(:raw_data) { Gitlab::Json.parse(fixture_file("GoogleCodeProjectHosting.json")) }
-
- subject { described_class.new(raw_data) }
-
- describe "#valid?" do
- context "when the data is valid" do
- it "returns true" do
- expect(subject).to be_valid
- end
- end
-
- context "when the data is invalid" do
- let(:raw_data) { "No clue" }
-
- it "returns true" do
- expect(subject).not_to be_valid
- end
- end
- end
-
- describe "#repos" do
- it "returns only Git repositories" do
- expect(subject.repos.length).to eq(1)
- expect(subject.incompatible_repos.length).to eq(1)
- end
- end
-
- describe "#repo" do
- it "returns the referenced repository" do
- expect(subject.repo("tint2").name).to eq("tint2")
- end
- end
-end
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
deleted file mode 100644
index a22e80ae1c0..00000000000
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# frozen_string_literal: true
-
-require "spec_helper"
-
-RSpec.describe Gitlab::GoogleCodeImport::Importer do
- let(:mapped_user) { create(:user, username: "thilo123") }
- let(:raw_data) { Gitlab::Json.parse(fixture_file("GoogleCodeProjectHosting.json")) }
- let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) }
- let(:import_data) do
- {
- 'repo' => client.repo('tint2').raw_data,
- 'user_map' => { 'thilo...' => "@#{mapped_user.username}" }
- }
- end
-
- let(:project) { create(:project) }
-
- subject { described_class.new(project) }
-
- before do
- project.add_maintainer(project.creator)
- project.create_import_data(data: import_data)
- end
-
- describe "#execute" do
- it "imports status labels" do
- subject.execute
-
- %w(New NeedInfo Accepted Wishlist Started Fixed Invalid Duplicate WontFix Incomplete).each do |status|
- expect(project.labels.find_by(name: "Status: #{status}")).not_to be_nil
- end
- end
-
- it "imports labels" do
- subject.execute
-
- %w(
- Type-Defect Type-Enhancement Type-Task Type-Review Type-Other Milestone-0.12 Priority-Critical
- Priority-High Priority-Medium Priority-Low OpSys-All OpSys-Windows OpSys-Linux OpSys-OSX Security
- Performance Usability Maintainability Component-Panel Component-Taskbar Component-Battery
- Component-Systray Component-Clock Component-Launcher Component-Tint2conf Component-Docs Component-New
- ).each do |label|
- label = label.sub("-", ": ")
- expect(project.labels.find_by(name: label)).not_to be_nil
- end
- end
-
- it "imports issues" do
- subject.execute
-
- issue = project.issues.first
- expect(issue).not_to be_nil
- expect(issue.iid).to eq(169)
- expect(issue.author).to eq(project.creator)
- expect(issue.assignees).to eq([mapped_user])
- expect(issue.state).to eq("closed")
- expect(issue.label_names).to include("Priority: Medium")
- expect(issue.label_names).to include("Status: Fixed")
- expect(issue.label_names).to include("Type: Enhancement")
- expect(issue.title).to eq("Scrolling through tasks")
- expect(issue.state).to eq("closed")
- expect(issue.description).to include("schattenpr\\.\\.\\.")
- expect(issue.description).to include("November 18, 2009 00:20")
- expect(issue.description).to include("Google Code")
- expect(issue.description).to include('I like to scroll through the tasks with my scrollwheel (like in fluxbox).')
- expect(issue.description).to include('Patch is attached that adds two new mouse-actions (next_task+prev_task)')
- expect(issue.description).to include('that can be used for exactly that purpose.')
- expect(issue.description).to include('all the best!')
- expect(issue.description).to include('[tint2_task_scrolling.diff](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/tint2_task_scrolling.diff)')
- expect(issue.description).to include('![screenshot.png](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/screenshot.png)')
- expect(issue.description).to include('![screenshot1.PNG](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/screenshot1.PNG)')
- end
-
- it "imports issue comments" do
- subject.execute
-
- note = project.issues.first.notes.first
- expect(note).not_to be_nil
- expect(note.note).to include("Comment 1")
- expect(note.note).to include("@#{mapped_user.username}")
- expect(note.note).to include("November 18, 2009 05:14")
- expect(note.note).to include("applied, thanks.")
- expect(note.note).to include("Status: Fixed")
- expect(note.note).to include("~~Type: Defect~~")
- expect(note.note).to include("Type: Enhancement")
- end
- end
-end
diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
deleted file mode 100644
index cfebe57aed3..00000000000
--- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::GoogleCodeImport::ProjectCreator do
- let(:user) { create(:user) }
- let(:repo) do
- Gitlab::GoogleCodeImport::Repository.new(
- "name" => 'vim',
- "summary" => 'VI Improved',
- "repositoryUrls" => ["https://vim.googlecode.com/git/"]
- )
- end
-
- let(:namespace) { create(:group) }
-
- before do
- namespace.add_owner(user)
- end
-
- it 'creates project' do
- expect_next_instance_of(Project) do |project|
- expect(project).to receive(:add_import_job)
- end
-
- project_creator = described_class.new(repo, namespace, user)
- project = project_creator.execute
-
- expect(project.import_url).to eq("https://vim.googlecode.com/git/")
- expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
- end
-end
diff --git a/spec/lib/gitlab/graphql/docs/renderer_spec.rb b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
index d1be962a4f8..064e0c6828b 100644
--- a/spec/lib/gitlab/graphql/docs/renderer_spec.rb
+++ b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
Class.new(Types::BaseObject) do
graphql_name 'ArrayTest'
- field :foo, [GraphQL::STRING_TYPE], null: false, description: 'A description'
+ field :foo, [GraphQL::STRING_TYPE], null: false, description: 'A description.'
end
end
@@ -40,7 +40,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description |
| ----- | ---- | ----------- |
- | `foo` | String! => Array | A description |
+ | `foo` | String! => Array | A description. |
DOC
is_expected.to include(expectation)
@@ -52,8 +52,8 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
Class.new(Types::BaseObject) do
graphql_name 'OrderingTest'
- field :foo, GraphQL::STRING_TYPE, null: false, description: 'A description of foo field'
- field :bar, GraphQL::STRING_TYPE, null: false, description: 'A description of bar field'
+ field :foo, GraphQL::STRING_TYPE, null: false, description: 'A description of foo field.'
+ field :bar, GraphQL::STRING_TYPE, null: false, description: 'A description of bar field.'
end
end
@@ -63,8 +63,8 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description |
| ----- | ---- | ----------- |
- | `bar` | String! | A description of bar field |
- | `foo` | String! | A description of foo field |
+ | `bar` | String! | A description of bar field. |
+ | `foo` | String! | A description of foo field. |
DOC
is_expected.to include(expectation)
@@ -76,7 +76,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
Class.new(Types::BaseObject) do
graphql_name 'DeprecatedTest'
- field :foo, GraphQL::STRING_TYPE, null: false, deprecated: { reason: 'This is deprecated', milestone: '1.10' }, description: 'A description'
+ field :foo, GraphQL::STRING_TYPE, null: false, deprecated: { reason: 'This is deprecated', milestone: '1.10' }, description: 'A description.'
end
end
@@ -86,7 +86,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description |
| ----- | ---- | ----------- |
- | `foo` **{warning-solid}** | String! | **Deprecated:** This is deprecated. Deprecated in 1.10 |
+ | `foo` **{warning-solid}** | String! | **Deprecated:** This is deprecated. Deprecated in 1.10. |
DOC
is_expected.to include(expectation)
@@ -98,14 +98,14 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
enum_type = Class.new(Types::BaseEnum) do
graphql_name 'MyEnum'
- value 'BAZ', description: 'A description of BAZ'
- value 'BAR', description: 'A description of BAR', deprecated: { reason: 'This is deprecated', milestone: '1.10' }
+ value 'BAZ', description: 'A description of BAZ.'
+ value 'BAR', description: 'A description of BAR.', deprecated: { reason: 'This is deprecated', milestone: '1.10' }
end
Class.new(Types::BaseObject) do
graphql_name 'EnumTest'
- field :foo, enum_type, null: false, description: 'A description of foo field'
+ field :foo, enum_type, null: false, description: 'A description of foo field.'
end
end
@@ -115,8 +115,8 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Value | Description |
| ----- | ----------- |
- | `BAR` **{warning-solid}** | **Deprecated:** This is deprecated. Deprecated in 1.10 |
- | `BAZ` | A description of BAZ |
+ | `BAR` **{warning-solid}** | **Deprecated:** This is deprecated. Deprecated in 1.10. |
+ | `BAZ` | A description of BAZ. |
DOC
is_expected.to include(expectation)
diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb
index 82090f992eb..0e36ea14ac3 100644
--- a/spec/lib/gitlab/graphql/markdown_field_spec.rb
+++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb
@@ -22,6 +22,8 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
.to raise_error(expected_error)
end
+ # TODO: remove as part of https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27536
+ # so that until that time, the developer check is there
it 'raises when passing a resolve block' do
expect { class_with_markdown_field(:test_html, null: true, resolve: -> (_, _, _) { 'not really' } ) }
.to raise_error(expected_error)
diff --git a/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb
new file mode 100644
index 00000000000..03cf53bb990
--- /dev/null
+++ b/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Gitlab::Graphql::Pagination::ArrayConnection do
+ let(:nodes) { (1..10) }
+
+ subject(:connection) { described_class.new(nodes, max_page_size: 100) }
+
+ it_behaves_like 'a connection with collection methods'
+
+ it_behaves_like 'a redactable connection' do
+ let(:unwanted) { 5 }
+ end
+end
diff --git a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
index 932bcd8cd92..d2475d1edb9 100644
--- a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
@@ -10,7 +10,13 @@ RSpec.describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection d
let(:arguments) { {} }
subject(:connection) do
- described_class.new(all_nodes, { max_page_size: values.size }.merge(arguments))
+ described_class.new(all_nodes, **{ max_page_size: values.size }.merge(arguments))
+ end
+
+ it_behaves_like 'a connection with collection methods'
+
+ it_behaves_like 'a redactable connection' do
+ let(:unwanted) { 3 }
end
describe '#nodes' do
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
index c8f368b15fc..0ac54a20fcc 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
@@ -10,17 +10,24 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil, object: nil) }
subject(:connection) do
- described_class.new(nodes, { context: context, max_page_size: 3 }.merge(arguments))
+ described_class.new(nodes, **{ context: context, max_page_size: 3 }.merge(arguments))
end
def encoded_cursor(node)
- described_class.new(nodes, { context: context }).cursor_for(node)
+ described_class.new(nodes, context: context).cursor_for(node)
end
def decoded_cursor(cursor)
Gitlab::Json.parse(Base64Bp.urlsafe_decode64(cursor))
end
+ it_behaves_like 'a connection with collection methods'
+
+ it_behaves_like 'a redactable connection' do
+ let_it_be(:projects) { create_list(:project, 2) }
+ let(:unwanted) { projects.second }
+ end
+
describe '#cursor_for' do
let(:project) { create(:project) }
let(:cursor) { connection.cursor_for(project) }
diff --git a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
index 86f35de94ed..1ca7c1c3c69 100644
--- a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
@@ -6,4 +6,15 @@ RSpec.describe Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection
it 'subclasses from GraphQL::Relay::RelationConnection' do
expect(described_class.superclass).to eq GraphQL::Pagination::ActiveRecordRelationConnection
end
+
+ it_behaves_like 'a connection with collection methods' do
+ let(:connection) { described_class.new(Project.all) }
+ end
+
+ it_behaves_like 'a redactable connection' do
+ let_it_be(:users) { create_list(:user, 2) }
+
+ let(:connection) { described_class.new(User.all, max_page_size: 10) }
+ let(:unwanted) { users.second }
+ end
end
diff --git a/spec/lib/gitlab/graphql/timeout_spec.rb b/spec/lib/gitlab/graphql/timeout_spec.rb
index 3669a89ba7c..999840019d2 100644
--- a/spec/lib/gitlab/graphql/timeout_spec.rb
+++ b/spec/lib/gitlab/graphql/timeout_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Timeout do
- it 'inherits from ' do
+ it 'inherits from' do
expect(described_class.superclass).to eq GraphQL::Schema::Timeout
end
diff --git a/spec/lib/gitlab/hook_data/group_member_builder_spec.rb b/spec/lib/gitlab/hook_data/group_member_builder_spec.rb
new file mode 100644
index 00000000000..78c62fd23c7
--- /dev/null
+++ b/spec/lib/gitlab/hook_data/group_member_builder_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::HookData::GroupMemberBuilder do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:group_member) { create(:group_member, :developer, group: group, expires_at: 1.day.from_now) }
+
+ describe '#build' do
+ let(:data) { described_class.new(group_member).build(event) }
+ let(:event_name) { data[:event_name] }
+ let(:attributes) do
+ [
+ :event_name, :created_at, :updated_at, :expires_at, :group_name, :group_path,
+ :group_id, :user_id, :user_username, :user_name, :user_email, :group_access
+ ]
+ end
+
+ context 'data' do
+ shared_examples_for 'includes the required attributes' do
+ it 'includes the required attributes' do
+ expect(data).to include(*attributes)
+
+ expect(data[:group_name]).to eq(group.name)
+ expect(data[:group_path]).to eq(group.path)
+ expect(data[:group_id]).to eq(group.id)
+ expect(data[:user_username]).to eq(group_member.user.username)
+ expect(data[:user_name]).to eq(group_member.user.name)
+ expect(data[:user_email]).to eq(group_member.user.email)
+ expect(data[:user_id]).to eq(group_member.user.id)
+ expect(data[:group_access]).to eq('Developer')
+ expect(data[:created_at]).to eq(group_member.created_at&.xmlschema)
+ expect(data[:updated_at]).to eq(group_member.updated_at&.xmlschema)
+ expect(data[:expires_at]).to eq(group_member.expires_at&.xmlschema)
+ end
+ end
+
+ context 'on create' do
+ let(:event) { :create }
+
+ it { expect(event_name).to eq('user_add_to_group') }
+ it_behaves_like 'includes the required attributes'
+ end
+
+ context 'on update' do
+ let(:event) { :update }
+
+ it { expect(event_name).to eq('user_update_for_group') }
+ it_behaves_like 'includes the required attributes'
+ end
+
+ context 'on destroy' do
+ let(:event) { :destroy }
+
+ it { expect(event_name).to eq('user_remove_from_group') }
+ it_behaves_like 'includes the required attributes'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/i18n/po_linter_spec.rb b/spec/lib/gitlab/i18n/po_linter_spec.rb
index e04c0b49480..f2ee6bb72d9 100644
--- a/spec/lib/gitlab/i18n/po_linter_spec.rb
+++ b/spec/lib/gitlab/i18n/po_linter_spec.rb
@@ -6,7 +6,7 @@ require 'simple_po_parser'
# Disabling this cop to allow for multi-language examples in comments
# rubocop:disable Style/AsciiComments
RSpec.describe Gitlab::I18n::PoLinter do
- let(:linter) { described_class.new(po_path: po_path, html_todolist: {}) }
+ let(:linter) { described_class.new(po_path: po_path) }
let(:po_path) { 'spec/fixtures/valid.po' }
def fake_translation(msgid:, translation:, plural_id: nil, plurals: [])
@@ -24,8 +24,7 @@ RSpec.describe Gitlab::I18n::PoLinter do
Gitlab::I18n::TranslationEntry.new(
entry_data: data,
- nplurals: plurals.size + 1,
- html_allowed: nil
+ nplurals: plurals.size + 1
)
end
@@ -160,53 +159,6 @@ RSpec.describe Gitlab::I18n::PoLinter do
]
end
end
-
- context 'when an entry contains html on the todolist' do
- subject(:linter) { described_class.new(po_path: po_path, html_todolist: todolist) }
-
- let(:po_path) { 'spec/fixtures/potential_html.po' }
- let(:todolist) do
- {
- 'String with a legitimate < use' => {
- 'plural_id' => 'String with lots of < > uses',
- 'translations' => [
- 'Translated string with a legitimate < use',
- 'Translated string with lots of < > uses'
- ]
- }
- }
- end
-
- it 'does not present an error' do
- message_id = 'String with a legitimate < use'
-
- expect(errors[message_id]).to be_nil
- end
- end
-
- context 'when an entry on the html todolist has changed' do
- subject(:linter) { described_class.new(po_path: po_path, html_todolist: todolist) }
-
- let(:po_path) { 'spec/fixtures/potential_html.po' }
- let(:todolist) do
- {
- 'String with a legitimate < use' => {
- 'plural_id' => 'String with lots of < > uses',
- 'translations' => [
- 'Translated string with a different legitimate < use',
- 'Translated string with lots of < > uses'
- ]
- }
- }
- end
-
- it 'presents an error for the changed component' do
- message_id = 'String with a legitimate < use'
-
- expect(errors[message_id])
- .to include a_string_starting_with('translation contains < or >.')
- end
- end
end
describe '#parse_po' do
@@ -276,8 +228,7 @@ RSpec.describe Gitlab::I18n::PoLinter do
fake_entry = Gitlab::I18n::TranslationEntry.new(
entry_data: { msgid: 'the singular', msgid_plural: 'the plural', 'msgstr[0]' => 'the singular' },
- nplurals: 2,
- html_allowed: nil
+ nplurals: 2
)
errors = []
diff --git a/spec/lib/gitlab/i18n/translation_entry_spec.rb b/spec/lib/gitlab/i18n/translation_entry_spec.rb
index 2c95b0b0124..f05346d07d3 100644
--- a/spec/lib/gitlab/i18n/translation_entry_spec.rb
+++ b/spec/lib/gitlab/i18n/translation_entry_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#singular_translation' do
it 'returns the normal `msgstr` for translations without plural' do
data = { msgid: 'Hello world', msgstr: 'Bonjour monde' }
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry.singular_translation).to eq('Bonjour monde')
end
@@ -18,7 +18,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
'msgstr[0]' => 'Bonjour monde',
'msgstr[1]' => 'Bonjour mondes'
}
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry.singular_translation).to eq('Bonjour monde')
end
@@ -27,7 +27,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#all_translations' do
it 'returns all translations for singular translations' do
data = { msgid: 'Hello world', msgstr: 'Bonjour monde' }
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry.all_translations).to eq(['Bonjour monde'])
end
@@ -39,7 +39,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
'msgstr[0]' => 'Bonjour monde',
'msgstr[1]' => 'Bonjour mondes'
}
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry.all_translations).to eq(['Bonjour monde', 'Bonjour mondes'])
end
@@ -52,7 +52,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
msgid_plural: 'Hello worlds',
'msgstr[0]' => 'Bonjour monde'
}
- entry = described_class.new(entry_data: data, nplurals: 1, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 1)
expect(entry.plural_translations).to eq(['Bonjour monde'])
end
@@ -65,7 +65,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
'msgstr[1]' => 'Bonjour mondes',
'msgstr[2]' => 'Bonjour tous les mondes'
}
- entry = described_class.new(entry_data: data, nplurals: 3, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 3)
expect(entry.plural_translations).to eq(['Bonjour mondes', 'Bonjour tous les mondes'])
end
@@ -77,7 +77,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
msgid: 'hello world',
msgstr: 'hello'
}
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry).to have_singular_translation
end
@@ -89,7 +89,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
"msgstr[0]" => 'hello world',
"msgstr[1]" => 'hello worlds'
}
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry).to have_singular_translation
end
@@ -100,7 +100,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
msgid_plural: 'hello worlds',
"msgstr[0]" => 'hello worlds'
}
- entry = described_class.new(entry_data: data, nplurals: 1, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 1)
expect(entry).not_to have_singular_translation
end
@@ -109,7 +109,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#msgid_contains_newlines' do
it 'is true when the msgid is an array' do
data = { msgid: %w(hello world) }
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry.msgid_has_multiple_lines?).to be_truthy
end
@@ -118,7 +118,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#plural_id_contains_newlines' do
it 'is true when the msgid is an array' do
data = { msgid_plural: %w(hello world) }
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry.plural_id_has_multiple_lines?).to be_truthy
end
@@ -127,7 +127,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#translations_contain_newlines' do
it 'is true when the msgid is an array' do
data = { msgstr: %w(hello world) }
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry.translations_have_multiple_lines?).to be_truthy
end
@@ -135,7 +135,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#contains_unescaped_chars' do
let(:data) { { msgid: '' } }
- let(:entry) { described_class.new(entry_data: data, nplurals: 2, html_allowed: nil) }
+ let(:entry) { described_class.new(entry_data: data, nplurals: 2) }
it 'is true when the msgid is an array' do
string = '「100%確定」'
@@ -177,7 +177,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#msgid_contains_unescaped_chars' do
it 'is true when the msgid contains a `%`' do
data = { msgid: '「100%確定」' }
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry).to receive(:contains_unescaped_chars?).and_call_original
expect(entry.msgid_contains_unescaped_chars?).to be_truthy
@@ -187,7 +187,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#plural_id_contains_unescaped_chars' do
it 'is true when the plural msgid contains a `%`' do
data = { msgid_plural: '「100%確定」' }
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry).to receive(:contains_unescaped_chars?).and_call_original
expect(entry.plural_id_contains_unescaped_chars?).to be_truthy
@@ -197,7 +197,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#translations_contain_unescaped_chars' do
it 'is true when the translation contains a `%`' do
data = { msgstr: '「100%確定」' }
- entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
+ entry = described_class.new(entry_data: data, nplurals: 2)
expect(entry).to receive(:contains_unescaped_chars?).and_call_original
expect(entry.translations_contain_unescaped_chars?).to be_truthy
@@ -205,7 +205,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
end
describe '#msgid_contains_potential_html?' do
- subject(:entry) { described_class.new(entry_data: data, nplurals: 2, html_allowed: nil) }
+ subject(:entry) { described_class.new(entry_data: data, nplurals: 2) }
context 'when there are no angle brackets in the msgid' do
let(:data) { { msgid: 'String with no brackets' } }
@@ -225,7 +225,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
end
describe '#plural_id_contains_potential_html?' do
- subject(:entry) { described_class.new(entry_data: data, nplurals: 2, html_allowed: nil) }
+ subject(:entry) { described_class.new(entry_data: data, nplurals: 2) }
context 'when there are no angle brackets in the plural_id' do
let(:data) { { msgid_plural: 'String with no brackets' } }
@@ -245,7 +245,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
end
describe '#translations_contain_potential_html?' do
- subject(:entry) { described_class.new(entry_data: data, nplurals: 2, html_allowed: nil) }
+ subject(:entry) { described_class.new(entry_data: data, nplurals: 2) }
context 'when there are no angle brackets in the translations' do
let(:data) { { msgstr: 'This string has no angle brackets' } }
@@ -263,78 +263,4 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
end
end
end
-
- describe '#msgid_html_allowed?' do
- subject(:entry) do
- described_class.new(entry_data: { msgid: 'String with a <strong>' }, nplurals: 2, html_allowed: html_todo)
- end
-
- context 'when the html in the string is in the todolist' do
- let(:html_todo) { { 'plural_id' => nil, 'translations' => [] } }
-
- it 'returns true' do
- expect(entry.msgid_html_allowed?).to be true
- end
- end
-
- context 'when the html in the string is not in the todolist' do
- let(:html_todo) { nil }
-
- it 'returns false' do
- expect(entry.msgid_html_allowed?).to be false
- end
- end
- end
-
- describe '#plural_id_html_allowed?' do
- subject(:entry) do
- described_class.new(entry_data: { msgid_plural: 'String with many <strong>' }, nplurals: 2, html_allowed: html_todo)
- end
-
- context 'when the html in the string is in the todolist' do
- let(:html_todo) { { 'plural_id' => 'String with many <strong>', 'translations' => [] } }
-
- it 'returns true' do
- expect(entry.plural_id_html_allowed?).to be true
- end
- end
-
- context 'when the html in the string is not in the todolist' do
- let(:html_todo) { { 'plural_id' => 'String with some <strong>', 'translations' => [] } }
-
- it 'returns false' do
- expect(entry.plural_id_html_allowed?).to be false
- end
- end
- end
-
- describe '#translations_html_allowed?' do
- subject(:entry) do
- described_class.new(entry_data: { msgstr: 'String with a <strong>' }, nplurals: 2, html_allowed: html_todo)
- end
-
- context 'when the html in the string is in the todolist' do
- let(:html_todo) { { 'plural_id' => nil, 'translations' => ['String with a <strong>'] } }
-
- it 'returns true' do
- expect(entry.translations_html_allowed?).to be true
- end
- end
-
- context 'when the html in the string is not in the todolist' do
- let(:html_todo) { { 'plural_id' => nil, 'translations' => ['String with a different <strong>'] } }
-
- it 'returns false' do
- expect(entry.translations_html_allowed?).to be false
- end
- end
-
- context 'when the todolist only has the msgid' do
- let(:html_todo) { { 'plural_id' => nil, 'translations' => nil } }
-
- it 'returns false' do
- expect(entry.translations_html_allowed?).to be false
- end
- end
- end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 38fe2781331..fba32ae0673 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -28,6 +28,7 @@ issues:
- events
- merge_requests_closing_issues
- metrics
+- metric_images
- timelogs
- issuable_severity
- issuable_sla
@@ -85,6 +86,7 @@ label:
- issues
- merge_requests
- priorities
+- epic_board_labels
milestone:
- group
- project
@@ -105,6 +107,7 @@ snippets:
- user_mentions
- snippet_repository
- statistics
+- repository_storage_moves
releases:
- author
- project
@@ -349,6 +352,7 @@ project:
- services
- campfire_service
- confluence_service
+- datadog_service
- discord_service
- drone_ci_service
- emails_on_push_service
@@ -540,6 +544,7 @@ project:
- daily_build_group_report_results
- jira_imports
- compliance_framework_setting
+- compliance_management_frameworks
- metrics_users_starred_dashboards
- alert_management_alerts
- repository_storage_moves
@@ -548,10 +553,13 @@ project:
- build_report_results
- vulnerability_statistic
- vulnerability_historical_statistics
+- vulnerability_remediations
- product_analytics_events
- pipeline_artifacts
- terraform_states
- alert_management_http_integrations
+- exported_protected_branches
+- incident_management_oncall_schedules
award_emoji:
- awardable
- user
@@ -639,6 +647,7 @@ boards:
- lists
- destroyable_lists
- milestone
+- iteration
- board_labels
- board_assignee
- assignee
@@ -648,6 +657,7 @@ boards:
lists:
- user
- milestone
+- iteration
- board
- label
- list_user_preferences
diff --git a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
index 2eb983cc050..2794acb8980 100644
--- a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
@@ -75,12 +75,31 @@ RSpec.describe Gitlab::ImportExport::Group::TreeRestorer do
before do
setup_import_export_config('group_exports/child_with_no_parent')
+ end
+
+ it 'captures import failures when a child group does not have a valid parent_id' do
+ group_tree_restorer.restore
- expect(group_tree_restorer.restore).to be_falsey
+ expect(group.import_failures.first.exception_message).to eq('Parent group not found')
end
+ end
+
+ context 'when child group creation fails' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:shared) { Gitlab::ImportExport::Shared.new(group) }
+ let(:group_tree_restorer) { described_class.new(user: user, shared: shared, group: group) }
+
+ before do
+ setup_import_export_config('group_exports/child_short_name')
+ end
+
+ it 'captures import failure' do
+ exception_message = 'Validation failed: Group URL is too short (minimum is 2 characters)'
+
+ group_tree_restorer.restore
- it 'fails when a child group does not have a valid parent_id' do
- expect(shared.errors).to include('Parent group not found')
+ expect(group.import_failures.first.exception_message).to eq(exception_message)
end
end
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 0db038785d3..75db3167ebc 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -48,7 +48,6 @@ RSpec.describe Gitlab::ImportExport::Importer do
[
Gitlab::ImportExport::AvatarRestorer,
Gitlab::ImportExport::RepoRestorer,
- Gitlab::ImportExport::WikiRestorer,
Gitlab::ImportExport::UploadsRestorer,
Gitlab::ImportExport::LfsRestorer,
Gitlab::ImportExport::StatisticsRestorer,
@@ -65,6 +64,20 @@ RSpec.describe Gitlab::ImportExport::Importer do
end
end
+ it 'calls RepoRestorer with project and wiki' do
+ wiki_repo_path = File.join(shared.export_path, Gitlab::ImportExport.wiki_repo_bundle_filename)
+ repo_path = File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename)
+ restorer = double(Gitlab::ImportExport::RepoRestorer)
+
+ expect(Gitlab::ImportExport::RepoRestorer).to receive(:new).with(path_to_bundle: repo_path, shared: shared, project: project).and_return(restorer)
+ expect(Gitlab::ImportExport::RepoRestorer).to receive(:new).with(path_to_bundle: wiki_repo_path, shared: shared, project: ProjectWiki.new(project)).and_return(restorer)
+ expect(Gitlab::ImportExport::RepoRestorer).to receive(:new).and_call_original
+
+ expect(restorer).to receive(:restore).and_return(true).twice
+
+ importer.execute
+ end
+
context 'with sample_data_template' do
it 'initializes the Sample::TreeRestorer' do
project.create_or_update_import_data(data: { sample_data: true })
diff --git a/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb b/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
index 0af74dee604..2a5e802bdc5 100644
--- a/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Gitlab::ImportExport::JSON::NdjsonWriter do
describe "#write_relation" do
context "when single relation is serialized" do
- it "appends json in correct file " do
+ it "appends json in correct file" do
relation = "relation"
value = { "key" => "value_1", "key_1" => "value_1" }
subject.write_relation(exportable_path, relation, value)
diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
index fd3b71deb37..e2bf87bf29f 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -674,10 +674,12 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
end
it 'does not allow setting params that are excluded from import_export settings' do
- project.create_import_data(data: { override_params: { lfs_enabled: true } })
+ original_value = project.lfs_enabled?
+
+ project.create_import_data(data: { override_params: { lfs_enabled: !original_value } })
expect(restored_project_json).to eq(true)
- expect(project.lfs_enabled).to be_falsey
+ expect(project.lfs_enabled).to eq(original_value)
end
it 'overrides project feature access levels' do
diff --git a/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
index bd9ac6d6697..d3c14b1f8fe 100644
--- a/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
@@ -113,12 +113,31 @@ RSpec.describe Gitlab::ImportExport::RelationTreeRestorer do
include_examples 'logging of relations creation'
end
+ end
+
+ context 'using ndjson reader' do
+ let(:path) { 'spec/fixtures/lib/gitlab/import_export/complex/tree' }
+ let(:relation_reader) { Gitlab::ImportExport::JSON::NdjsonReader.new(path) }
+
+ it_behaves_like 'import project successfully'
+ end
- context 'using ndjson reader' do
- let(:path) { 'spec/fixtures/lib/gitlab/import_export/complex/tree' }
- let(:relation_reader) { Gitlab::ImportExport::JSON::NdjsonReader.new(path) }
+ context 'with invalid relations' do
+ let(:path) { 'spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree' }
+ let(:relation_reader) { Gitlab::ImportExport::JSON::NdjsonReader.new(path) }
- it_behaves_like 'import project successfully'
+ it 'logs the invalid relation and its errors' do
+ expect(relation_tree_restorer.shared.logger)
+ .to receive(:warn)
+ .with(
+ error_messages: "Title can't be blank. Title is invalid",
+ message: '[Project/Group Import] Invalid object relation built',
+ relation_class: 'ProjectLabel',
+ relation_index: 0,
+ relation_key: 'labels'
+ ).once
+
+ relation_tree_restorer.restore
end
end
end
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index b32ae60fbcc..a6b917457c2 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -5,35 +5,42 @@ require 'spec_helper'
RSpec.describe Gitlab::ImportExport::RepoRestorer do
include GitHelpers
+ let_it_be(:project_with_repo) do
+ create(:project, :repository, :wiki_repo, name: 'test-repo-restorer', path: 'test-repo-restorer').tap do |p|
+ p.wiki.create_page('page', 'foobar', :markdown, 'created page')
+ end
+ end
+
+ let!(:project) { create(:project) }
+
+ let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
+ let(:shared) { project.import_export_shared }
+
+ before do
+ allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+
+ bundler.save
+ end
+
+ after do
+ FileUtils.rm_rf(export_path)
+ end
+
describe 'bundle a project Git repo' do
- let(:user) { create(:user) }
- let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
- let!(:project) { create(:project) }
- let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
- let(:shared) { project.import_export_shared }
let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
subject { described_class.new(path_to_bundle: bundle_path, shared: shared, project: project) }
- before do
- allow_next_instance_of(Gitlab::ImportExport) do |instance|
- allow(instance).to receive(:storage_path).and_return(export_path)
- end
-
- bundler.save
- end
-
after do
- FileUtils.rm_rf(export_path)
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- FileUtils.rm_rf(project_with_repo.repository.path_to_repo)
- FileUtils.rm_rf(project.repository.path_to_repo)
- end
+ Gitlab::Shell.new.remove_repository(project.repository_storage, project.disk_path)
end
it 'restores the repo successfully' do
+ expect(project.repository.exists?).to be false
expect(subject.restore).to be_truthy
+
+ expect(project.repository.empty?).to be false
end
context 'when the repository already exists' do
@@ -53,4 +60,35 @@ RSpec.describe Gitlab::ImportExport::RepoRestorer do
end
end
end
+
+ describe 'restore a wiki Git repo' do
+ let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_with_repo, shared: shared) }
+ let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.wiki_repo_bundle_filename) }
+
+ subject { described_class.new(path_to_bundle: bundle_path, shared: shared, project: ProjectWiki.new(project)) }
+
+ after do
+ Gitlab::Shell.new.remove_repository(project.wiki.repository_storage, project.wiki.disk_path)
+ end
+
+ it 'restores the wiki repo successfully' do
+ expect(project.wiki_repository_exists?).to be false
+
+ subject.restore
+ project.wiki.repository.expire_status_cache
+
+ expect(project.wiki_repository_exists?).to be true
+ end
+
+ describe 'no wiki in the bundle' do
+ let!(:project_without_wiki) { create(:project) }
+
+ let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_without_wiki, shared: shared) }
+
+ it 'does not creates an empty wiki' do
+ expect(subject.restore).to be true
+ expect(project.wiki_repository_exists?).to be false
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index b33462b4096..a93ee051ccf 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -26,7 +26,7 @@ Issue:
- weight
- time_estimate
- relative_position
-- service_desk_reply_to
+- external_author
- last_edited_at
- last_edited_by_id
- discussion_locked
@@ -219,6 +219,7 @@ MergeRequestDiff:
- start_commit_sha
- commits_count
- files_count
+- sorted
MergeRequestDiffCommit:
- merge_request_diff_id
- relative_order
@@ -577,6 +578,8 @@ ProjectFeature:
- pages_access_level
- metrics_dashboard_access_level
- requirements_access_level
+- analytics_access_level
+- operations_access_level
- created_at
- updated_at
ProtectedBranch::MergeAccessLevel:
@@ -742,6 +745,7 @@ Board:
- updated_at
- group_id
- milestone_id
+- iteration_id
- weight
- name
- hide_backlog_list
diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
deleted file mode 100644
index 6c80c410d07..00000000000
--- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::ImportExport::WikiRestorer do
- describe 'restore a wiki Git repo' do
- let!(:project_with_wiki) { create(:project, :wiki_repo) }
- let!(:project_without_wiki) { create(:project) }
- let!(:project) { create(:project) }
- let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
- let(:shared) { project.import_export_shared }
- let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_with_wiki, shared: shared) }
- let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
- let(:restorer) do
- described_class.new(path_to_bundle: bundle_path,
- shared: shared,
- project: project.wiki,
- wiki_enabled: true)
- end
-
- before do
- allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
-
- bundler.save
- end
-
- after do
- FileUtils.rm_rf(export_path)
- Gitlab::Shell.new.remove_repository(project_with_wiki.wiki.repository_storage, project_with_wiki.wiki.disk_path)
- Gitlab::Shell.new.remove_repository(project.wiki.repository_storage, project.wiki.disk_path)
- end
-
- it 'restores the wiki repo successfully' do
- expect(restorer.restore).to be true
- end
-
- describe "no wiki in the bundle" do
- let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_without_wiki, shared: shared) }
-
- it 'creates an empty wiki' do
- expect(restorer.restore).to be true
-
- expect(project.wiki_repository_exists?).to be true
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
index 0dfd8a2ee50..416d651b0de 100644
--- a/spec/lib/gitlab/import_sources_spec.rb
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -53,7 +53,6 @@ RSpec.describe Gitlab::ImportSources do
bitbucket
bitbucket_server
gitlab
- google_code
fogbugz
gitlab_project
gitea
@@ -70,7 +69,7 @@ RSpec.describe Gitlab::ImportSources do
'bitbucket' => Gitlab::BitbucketImport::Importer,
'bitbucket_server' => Gitlab::BitbucketServerImport::Importer,
'gitlab' => Gitlab::GitlabImport::Importer,
- 'google_code' => Gitlab::GoogleCodeImport::Importer,
+ 'google_code' => nil,
'fogbugz' => Gitlab::FogbugzImport::Importer,
'git' => nil,
'gitlab_project' => Gitlab::ImportExport::Importer,
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index 88f2def34d9..c00b0fdf043 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -34,7 +34,10 @@ RSpec.describe Gitlab::InstrumentationHelper do
:redis_shared_state_calls,
:redis_shared_state_duration_s,
:redis_shared_state_read_bytes,
- :redis_shared_state_write_bytes
+ :redis_shared_state_write_bytes,
+ :db_count,
+ :db_write_count,
+ :db_cached_count
]
expect(described_class.keys).to eq(expected_keys)
@@ -46,10 +49,10 @@ RSpec.describe Gitlab::InstrumentationHelper do
subject { described_class.add_instrumentation_data(payload) }
- it 'adds nothing' do
+ it 'adds only DB counts by default' do
subject
- expect(payload).to eq({})
+ expect(payload).to eq(db_count: 0, db_cached_count: 0, db_write_count: 0)
end
context 'when Gitaly calls are made' do
diff --git a/spec/lib/gitlab/kubernetes/deployment_spec.rb b/spec/lib/gitlab/kubernetes/deployment_spec.rb
new file mode 100644
index 00000000000..2433e854e5b
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/deployment_spec.rb
@@ -0,0 +1,190 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Kubernetes::Deployment do
+ include KubernetesHelpers
+
+ let(:pods) { {} }
+
+ subject(:deployment) { described_class.new(params, pods: pods) }
+
+ describe '#name' do
+ let(:params) { named(:selected) }
+
+ it { expect(deployment.name).to eq(:selected) }
+ end
+
+ describe '#labels' do
+ let(:params) { make('metadata', 'labels' => :selected) }
+
+ it { expect(deployment.labels).to eq(:selected) }
+ end
+
+ describe '#outdated?' do
+ context 'when outdated' do
+ let(:params) { generation(2, 1, 0) }
+
+ it { expect(deployment.outdated?).to be_truthy }
+ end
+
+ context 'when up to date' do
+ let(:params) { generation(2, 2, 0) }
+
+ it { expect(deployment.outdated?).to be_falsy }
+ end
+
+ context 'when ahead of latest' do
+ let(:params) { generation(1, 2, 0) }
+
+ it { expect(deployment.outdated?).to be_falsy }
+ end
+ end
+
+ describe '#instances' do
+ context 'when unnamed' do
+ let(:pods) do
+ [
+ kube_pod(name: nil, status: 'Pending'),
+ kube_pod(name: nil, status: 'Pending'),
+ kube_pod(name: nil, status: 'Pending'),
+ kube_pod(name: nil, status: 'Pending')
+ ]
+ end
+
+ let(:params) { combine(generation(1, 1, 4)) }
+
+ it 'returns all pods with generated names and pending' do
+ expected = [
+ { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true },
+ { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true },
+ { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true },
+ { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true }
+ ]
+
+ expect(deployment.instances).to eq(expected)
+ end
+ end
+
+ # When replica count is higher than pods it is considered that pod was not
+ # able to spawn for some reason like limited resources.
+ context 'when number of pods is less than wanted replicas' do
+ let(:wanted_replicas) { 3 }
+ let(:pods) { [kube_pod(name: nil, status: 'Running')] }
+ let(:params) { combine(generation(1, 1, wanted_replicas)) }
+
+ it 'returns not spawned pods as pending and unknown and running' do
+ expected = [
+ { status: 'running', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Running)', track: 'stable', stable: true },
+ { status: 'pending', pod_name: 'Not provided', tooltip: 'Not provided (Pending)', track: 'stable', stable: true },
+ { status: 'pending', pod_name: 'Not provided', tooltip: 'Not provided (Pending)', track: 'stable', stable: true }
+ ]
+
+ expect(deployment.instances).to eq(expected)
+ end
+ end
+
+ context 'when outdated' do
+ let(:pods) do
+ [
+ kube_pod(status: 'Pending'),
+ kube_pod(name: 'kube-pod1', status: 'Pending'),
+ kube_pod(name: 'kube-pod2', status: 'Pending'),
+ kube_pod(name: 'kube-pod3', status: 'Pending')
+ ]
+ end
+
+ let(:params) { combine(named('foo'), generation(1, 0, 4)) }
+
+ it 'returns all instances as named and waiting' do
+ expected = [
+ { status: 'pending', pod_name: 'kube-pod', tooltip: 'kube-pod (Pending)', track: 'stable', stable: true },
+ { status: 'pending', pod_name: 'kube-pod1', tooltip: 'kube-pod1 (Pending)', track: 'stable', stable: true },
+ { status: 'pending', pod_name: 'kube-pod2', tooltip: 'kube-pod2 (Pending)', track: 'stable', stable: true },
+ { status: 'pending', pod_name: 'kube-pod3', tooltip: 'kube-pod3 (Pending)', track: 'stable', stable: true }
+ ]
+
+ expect(deployment.instances).to eq(expected)
+ end
+ end
+
+ context 'with pods of each type' do
+ let(:pods) do
+ [
+ kube_pod(status: 'Succeeded'),
+ kube_pod(name: 'kube-pod1', status: 'Running'),
+ kube_pod(name: 'kube-pod2', status: 'Pending'),
+ kube_pod(name: 'kube-pod3', status: 'Pending')
+ ]
+ end
+
+ let(:params) { combine(named('foo'), generation(1, 1, 4)) }
+
+ it 'returns all instances' do
+ expected = [
+ { status: 'succeeded', pod_name: 'kube-pod', tooltip: 'kube-pod (Succeeded)', track: 'stable', stable: true },
+ { status: 'running', pod_name: 'kube-pod1', tooltip: 'kube-pod1 (Running)', track: 'stable', stable: true },
+ { status: 'pending', pod_name: 'kube-pod2', tooltip: 'kube-pod2 (Pending)', track: 'stable', stable: true },
+ { status: 'pending', pod_name: 'kube-pod3', tooltip: 'kube-pod3 (Pending)', track: 'stable', stable: true }
+ ]
+
+ expect(deployment.instances).to eq(expected)
+ end
+ end
+
+ context 'with track label' do
+ let(:pods) { [kube_pod(status: 'Pending')] }
+ let(:labels) { { 'track' => track } }
+ let(:params) { combine(named('foo', labels), generation(1, 0, 1)) }
+
+ context 'when marked as stable' do
+ let(:track) { 'stable' }
+
+ it 'returns all instances' do
+ expected = [
+ { status: 'pending', pod_name: 'kube-pod', tooltip: 'kube-pod (Pending)', track: 'stable', stable: true }
+ ]
+
+ expect(deployment.instances).to eq(expected)
+ end
+ end
+
+ context 'when marked as canary' do
+ let(:track) { 'canary' }
+ let(:pods) { [kube_pod(status: 'Pending', track: track)] }
+
+ it 'returns all instances' do
+ expected = [
+ { status: 'pending', pod_name: 'kube-pod', tooltip: 'kube-pod (Pending)', track: 'canary', stable: false }
+ ]
+
+ expect(deployment.instances).to eq(expected)
+ end
+ end
+ end
+ end
+
+ def generation(expected, observed, replicas)
+ combine(
+ make('metadata', 'generation' => expected),
+ make('status', 'observedGeneration' => observed),
+ make('spec', 'replicas' => replicas)
+ )
+ end
+
+ def named(name = "foo", labels = {})
+ make('metadata', 'name' => name, 'labels' => labels)
+ end
+
+ def make(key, values = {})
+ hsh = {}
+ hsh[key] = values
+ hsh
+ end
+
+ def combine(*hashes)
+ out = {}
+ hashes.each { |hsh| out = out.deep_merge(hsh) }
+ out
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb
index 9e580cea397..2a3a4cec2b0 100644
--- a/spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb
@@ -12,32 +12,14 @@ RSpec.describe Gitlab::Kubernetes::Helm::V2::ResetCommand do
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
- helm reset
- kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller
- kubectl delete clusterrolebinding tiller-admin
+ export HELM_HOST="localhost:44134"
+ tiller -listen ${HELM_HOST} -alsologtostderr &
+ helm init --client-only
+ helm reset --force
EOS
end
end
- context 'when there is a ca.pem file' do
- let(:files) { { 'ca.pem': 'some file content' } }
-
- it_behaves_like 'helm command generator' do
- let(:commands) do
- <<~EOS1.squish + "\n" + <<~EOS2
- helm reset
- --tls
- --tls-ca-cert /data/helm/helm/config/ca.pem
- --tls-cert /data/helm/helm/config/cert.pem
- --tls-key /data/helm/helm/config/key.pem
- EOS1
- kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller
- kubectl delete clusterrolebinding tiller-admin
- EOS2
- end
- end
- end
-
describe '#pod_name' do
subject { reset_command.pod_name }
diff --git a/spec/lib/gitlab/kubernetes/ingress_spec.rb b/spec/lib/gitlab/kubernetes/ingress_spec.rb
new file mode 100644
index 00000000000..e4d6bf4086f
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/ingress_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Kubernetes::Ingress do
+ include KubernetesHelpers
+
+ let(:ingress) { described_class.new(params) }
+
+ describe '#canary?' do
+ subject { ingress.canary? }
+
+ context 'with canary ingress parameters' do
+ let(:params) { canary_metadata }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'with stable ingress parameters' do
+ let(:params) { stable_metadata }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#canary_weight' do
+ subject { ingress.canary_weight }
+
+ context 'with canary ingress parameters' do
+ let(:params) { canary_metadata }
+
+ it { is_expected.to eq(50) }
+ end
+
+ context 'with stable ingress parameters' do
+ let(:params) { stable_metadata }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#name' do
+ subject { ingress.name }
+
+ let(:params) { stable_metadata }
+
+ it { is_expected.to eq('production-auto-deploy') }
+ end
+
+ def stable_metadata
+ kube_ingress(track: :stable)
+ end
+
+ def canary_metadata
+ kube_ingress(track: :canary)
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/rollout_instances_spec.rb b/spec/lib/gitlab/kubernetes/rollout_instances_spec.rb
new file mode 100644
index 00000000000..3ac97ddc75d
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/rollout_instances_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Kubernetes::RolloutInstances do
+ include KubernetesHelpers
+
+ def setup(deployments_attrs, pods_attrs)
+ deployments = deployments_attrs.map do |attrs|
+ ::Gitlab::Kubernetes::Deployment.new(attrs, pods: pods_attrs)
+ end
+
+ pods = pods_attrs.map do |attrs|
+ ::Gitlab::Kubernetes::Pod.new(attrs)
+ end
+
+ [deployments, pods]
+ end
+
+ describe '#pod_instances' do
+ it 'returns an instance for a deployment with one pod' do
+ deployments, pods = setup(
+ [kube_deployment(name: 'one', track: 'stable', replicas: 1)],
+ [kube_pod(name: 'one', status: 'Running', track: 'stable')]
+ )
+ rollout_instances = described_class.new(deployments, pods)
+
+ expect(rollout_instances.pod_instances).to eq([{
+ pod_name: 'one',
+ stable: true,
+ status: 'running',
+ tooltip: 'one (Running)',
+ track: 'stable'
+ }])
+ end
+
+ it 'returns a pending pod for a missing replica' do
+ deployments, pods = setup(
+ [kube_deployment(name: 'one', track: 'stable', replicas: 1)],
+ []
+ )
+ rollout_instances = described_class.new(deployments, pods)
+
+ expect(rollout_instances.pod_instances).to eq([{
+ pod_name: 'Not provided',
+ stable: true,
+ status: 'pending',
+ tooltip: 'Not provided (Pending)',
+ track: 'stable'
+ }])
+ end
+
+ it 'returns instances when there are two stable deployments' do
+ deployments, pods = setup([
+ kube_deployment(name: 'one', track: 'stable', replicas: 1),
+ kube_deployment(name: 'two', track: 'stable', replicas: 1)
+ ], [
+ kube_pod(name: 'one', status: 'Running', track: 'stable'),
+ kube_pod(name: 'two', status: 'Running', track: 'stable')
+ ])
+ rollout_instances = described_class.new(deployments, pods)
+
+ expect(rollout_instances.pod_instances).to eq([{
+ pod_name: 'one',
+ stable: true,
+ status: 'running',
+ tooltip: 'one (Running)',
+ track: 'stable'
+ }, {
+ pod_name: 'two',
+ stable: true,
+ status: 'running',
+ tooltip: 'two (Running)',
+ track: 'stable'
+ }])
+ end
+
+ it 'returns instances for two deployments with different tracks' do
+ deployments, pods = setup([
+ kube_deployment(name: 'one', track: 'mytrack', replicas: 1),
+ kube_deployment(name: 'two', track: 'othertrack', replicas: 1)
+ ], [
+ kube_pod(name: 'one', status: 'Running', track: 'mytrack'),
+ kube_pod(name: 'two', status: 'Running', track: 'othertrack')
+ ])
+ rollout_instances = described_class.new(deployments, pods)
+
+ expect(rollout_instances.pod_instances).to eq([{
+ pod_name: 'one',
+ stable: false,
+ status: 'running',
+ tooltip: 'one (Running)',
+ track: 'mytrack'
+ }, {
+ pod_name: 'two',
+ stable: false,
+ status: 'running',
+ tooltip: 'two (Running)',
+ track: 'othertrack'
+ }])
+ end
+
+ it 'sorts stable tracks after canary tracks' do
+ deployments, pods = setup([
+ kube_deployment(name: 'one', track: 'stable', replicas: 1),
+ kube_deployment(name: 'two', track: 'canary', replicas: 1)
+ ], [
+ kube_pod(name: 'one', status: 'Running', track: 'stable'),
+ kube_pod(name: 'two', status: 'Running', track: 'canary')
+ ])
+ rollout_instances = described_class.new(deployments, pods)
+
+ expect(rollout_instances.pod_instances).to eq([{
+ pod_name: 'two',
+ stable: false,
+ status: 'running',
+ tooltip: 'two (Running)',
+ track: 'canary'
+ }, {
+ pod_name: 'one',
+ stable: true,
+ status: 'running',
+ tooltip: 'one (Running)',
+ track: 'stable'
+ }])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/rollout_status_spec.rb b/spec/lib/gitlab/kubernetes/rollout_status_spec.rb
new file mode 100644
index 00000000000..8ed9fdd799c
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/rollout_status_spec.rb
@@ -0,0 +1,271 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Kubernetes::RolloutStatus do
+ include KubernetesHelpers
+
+ let(:track) { nil }
+ let(:specs) { specs_all_finished }
+
+ let(:pods) do
+ create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: "canary")
+ end
+
+ let(:ingresses) { [] }
+
+ let(:specs_all_finished) do
+ [
+ kube_deployment(name: 'one'),
+ kube_deployment(name: 'two', track: track)
+ ]
+ end
+
+ let(:specs_half_finished) do
+ [
+ kube_deployment(name: 'one'),
+ kube_deployment(name: 'two', track: track)
+ ]
+ end
+
+ subject(:rollout_status) { described_class.from_deployments(*specs, pods_attrs: pods, ingresses: ingresses) }
+
+ describe '#deployments' do
+ it 'stores the deployments' do
+ expect(rollout_status.deployments).to be_kind_of(Array)
+ expect(rollout_status.deployments.size).to eq(2)
+ expect(rollout_status.deployments.first).to be_kind_of(::Gitlab::Kubernetes::Deployment)
+ end
+ end
+
+ describe '#instances' do
+ context 'for stable track' do
+ let(:track) { "any" }
+
+ let(:pods) do
+ create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: "any")
+ end
+
+ it 'stores the union of deployment instances' do
+ expected = [
+ { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false },
+ { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false },
+ { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false },
+ { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true },
+ { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true },
+ { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }
+ ]
+
+ expect(rollout_status.instances).to eq(expected)
+ end
+ end
+
+ context 'for stable track' do
+ let(:track) { 'canary' }
+
+ let(:pods) do
+ create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track)
+ end
+
+ it 'sorts stable instances last' do
+ expected = [
+ { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'canary', stable: false },
+ { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'canary', stable: false },
+ { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'canary', stable: false },
+ { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true },
+ { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true },
+ { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }
+ ]
+
+ expect(rollout_status.instances).to eq(expected)
+ end
+ end
+ end
+
+ describe '#completion' do
+ subject { rollout_status.completion }
+
+ context 'when all instances are finished' do
+ let(:track) { 'canary' }
+
+ it { is_expected.to eq(100) }
+ end
+
+ context 'when half of the instances are finished' do
+ let(:track) { "canary" }
+
+ let(:pods) do
+ create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track, status: "Pending")
+ end
+
+ let(:specs) { specs_half_finished }
+
+ it { is_expected.to eq(50) }
+ end
+
+ context 'with one deployment' do
+ it 'sets the completion percentage when a deployment has more running pods than desired' do
+ deployments = [kube_deployment(name: 'one', track: 'one', replicas: 2)]
+ pods = create_pods(name: 'one', track: 'one', count: 3)
+ rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods)
+
+ expect(rollout_status.completion).to eq(100)
+ end
+ end
+
+ context 'with two deployments on different tracks' do
+ it 'sets the completion percentage when all pods are complete' do
+ deployments = [
+ kube_deployment(name: 'one', track: 'one', replicas: 2),
+ kube_deployment(name: 'two', track: 'two', replicas: 2)
+ ]
+ pods = create_pods(name: 'one', track: 'one', count: 2) + create_pods(name: 'two', track: 'two', count: 2)
+ rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods)
+
+ expect(rollout_status.completion).to eq(100)
+ end
+ end
+
+ context 'with two deployments that both have track set to "stable"' do
+ it 'sets the completion percentage when all pods are complete' do
+ deployments = [
+ kube_deployment(name: 'one', track: 'stable', replicas: 2),
+ kube_deployment(name: 'two', track: 'stable', replicas: 2)
+ ]
+ pods = create_pods(name: 'one', track: 'stable', count: 2) + create_pods(name: 'two', track: 'stable', count: 2)
+ rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods)
+
+ expect(rollout_status.completion).to eq(100)
+ end
+
+ it 'sets the completion percentage when no pods are complete' do
+ deployments = [
+ kube_deployment(name: 'one', track: 'stable', replicas: 3),
+ kube_deployment(name: 'two', track: 'stable', replicas: 7)
+ ]
+ rollout_status = described_class.from_deployments(*deployments, pods_attrs: [])
+
+ expect(rollout_status.completion).to eq(0)
+ end
+
+ it 'sets the completion percentage when a quarter of the pods are complete' do
+ deployments = [
+ kube_deployment(name: 'one', track: 'stable', replicas: 6),
+ kube_deployment(name: 'two', track: 'stable', replicas: 2)
+ ]
+ pods = create_pods(name: 'one', track: 'stable', count: 2)
+ rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods)
+
+ expect(rollout_status.completion).to eq(25)
+ end
+ end
+
+ context 'with two deployments, one with track set to "stable" and one with no track label' do
+ it 'sets the completion percentage when all pods are complete' do
+ deployments = [
+ kube_deployment(name: 'one', track: 'stable', replicas: 3),
+ kube_deployment(name: 'two', track: nil, replicas: 3)
+ ]
+ pods = create_pods(name: 'one', track: 'stable', count: 3) + create_pods(name: 'two', track: nil, count: 3)
+ rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods)
+
+ expect(rollout_status.completion).to eq(100)
+ end
+
+ it 'sets the completion percentage when no pods are complete' do
+ deployments = [
+ kube_deployment(name: 'one', track: 'stable', replicas: 1),
+ kube_deployment(name: 'two', track: nil, replicas: 1)
+ ]
+ rollout_status = described_class.from_deployments(*deployments, pods_attrs: [])
+
+ expect(rollout_status.completion).to eq(0)
+ end
+
+ it 'sets the completion percentage when a third of the pods are complete' do
+ deployments = [
+ kube_deployment(name: 'one', track: 'stable', replicas: 2),
+ kube_deployment(name: 'two', track: nil, replicas: 7)
+ ]
+ pods = create_pods(name: 'one', track: 'stable', count: 2) + create_pods(name: 'two', track: nil, count: 1)
+ rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods)
+
+ expect(rollout_status.completion).to eq(33)
+ end
+ end
+ end
+
+ describe '#complete?' do
+ subject { rollout_status.complete? }
+
+ context 'when all instances are finished' do
+ let(:track) { 'canary' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when half of the instances are finished' do
+ let(:track) { "canary" }
+
+ let(:pods) do
+ create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track, status: "Pending")
+ end
+
+ let(:specs) { specs_half_finished }
+
+ it { is_expected.to be_falsy}
+ end
+ end
+
+ describe '#found?' do
+ context 'when the specs are passed' do
+ it { is_expected.to be_found }
+ end
+
+ context 'when list of specs is empty' do
+ let(:specs) { [] }
+
+ it { is_expected.not_to be_found }
+ end
+ end
+
+ describe '.loading' do
+ subject { described_class.loading }
+
+ it { is_expected.to be_loading }
+ end
+
+ describe '#not_found?' do
+ context 'when the specs are passed' do
+ it { is_expected.not_to be_not_found }
+ end
+
+ context 'when list of specs is empty' do
+ let(:specs) { [] }
+
+ it { is_expected.to be_not_found }
+ end
+ end
+
+ describe '#canary_ingress_exists?' do
+ context 'when canary ingress exists' do
+ let(:ingresses) { [kube_ingress(track: :canary)] }
+
+ it 'returns true' do
+ expect(rollout_status.canary_ingress_exists?).to eq(true)
+ end
+ end
+
+ context 'when canary ingress does not exist' do
+ let(:ingresses) { [kube_ingress(track: :stable)] }
+
+ it 'returns false' do
+ expect(rollout_status.canary_ingress_exists?).to eq(false)
+ end
+ end
+ end
+
+ def create_pods(name:, count:, track: nil, status: 'Running' )
+ Array.new(count, kube_pod(name: name, status: status, track: track))
+ end
+end
diff --git a/spec/lib/gitlab/metrics/background_transaction_spec.rb b/spec/lib/gitlab/metrics/background_transaction_spec.rb
deleted file mode 100644
index b2a53fe1626..00000000000
--- a/spec/lib/gitlab/metrics/background_transaction_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::BackgroundTransaction do
- let(:test_worker_class) { double(:class, name: 'TestWorker') }
- let(:prometheus_metric) { instance_double(Prometheus::Client::Metric, base_labels: {}) }
-
- before do
- allow(described_class).to receive(:prometheus_metric).and_return(prometheus_metric)
- end
-
- subject { described_class.new(test_worker_class) }
-
- RSpec.shared_examples 'metric with worker labels' do |metric_method|
- it 'measures with correct labels and value' do
- value = 1
- expect(prometheus_metric).to receive(metric_method).with({ controller: 'TestWorker', action: 'perform', feature_category: '' }, value)
-
- subject.send(metric_method, :bau, value)
- end
- end
-
- describe '#label' do
- it 'returns labels based on class name' do
- expect(subject.labels).to eq(controller: 'TestWorker', action: 'perform', feature_category: '')
- end
-
- it 'contains only the labels defined for metrics' do
- expect(subject.labels.keys).to contain_exactly(*described_class.superclass::BASE_LABEL_KEYS)
- end
-
- it 'includes the feature category if there is one' do
- expect(test_worker_class).to receive(:get_feature_category).and_return('source_code_management')
- expect(subject.labels).to include(feature_category: 'source_code_management')
- end
- end
-
- describe '#increment' do
- let(:prometheus_metric) { instance_double(Prometheus::Client::Counter, :increment, base_labels: {}) }
-
- it_behaves_like 'metric with worker labels', :increment
- end
-
- describe '#set' do
- let(:prometheus_metric) { instance_double(Prometheus::Client::Gauge, :set, base_labels: {}) }
-
- it_behaves_like 'metric with worker labels', :set
- end
-
- describe '#observe' do
- let(:prometheus_metric) { instance_double(Prometheus::Client::Histogram, :observe, base_labels: {}) }
-
- it_behaves_like 'metric with worker labels', :observe
- end
-end
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
deleted file mode 100644
index 047d1e5d205..00000000000
--- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::SidekiqMiddleware do
- let(:middleware) { described_class.new }
- let(:message) { { 'args' => ['test'], 'enqueued_at' => Time.new(2016, 6, 23, 6, 59).to_f } }
-
- describe '#call' do
- it 'tracks the transaction' do
- worker = double(:worker, class: double(:class, name: 'TestWorker'))
-
- expect_next_instance_of(Gitlab::Metrics::BackgroundTransaction) do |transaction|
- expect(transaction).to receive(:set).with(:gitlab_transaction_sidekiq_queue_duration_total, instance_of(Float))
- expect(transaction).to receive(:increment).with(:gitlab_transaction_db_count_total, 1)
- end
-
- middleware.call(worker, message, :test) do
- ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
- end
- end
-
- it 'prevents database counters from leaking to the next transaction' do
- worker = double(:worker, class: double(:class, name: 'TestWorker'))
-
- 2.times do
- Gitlab::WithRequestStore.with_request_store do
- middleware.call(worker, message, :test) do
- ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
- end
- end
- end
-
- expect(message).to include(db_count: 1, db_write_count: 0, db_cached_count: 0)
- end
-
- it 'tracks the transaction (for messages without `enqueued_at`)', :aggregate_failures do
- worker = double(:worker, class: double(:class, name: 'TestWorker'))
-
- expect(Gitlab::Metrics::BackgroundTransaction).to receive(:new)
- .with(worker.class)
- .and_call_original
-
- expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set)
- .with(:gitlab_transaction_sidekiq_queue_duration_total, instance_of(Float))
-
- middleware.call(worker, {}, :test) { nil }
- end
-
- it 'tracks any raised exceptions', :aggregate_failures, :request_store do
- worker = double(:worker, class: double(:class, name: 'TestWorker'))
-
- expect_any_instance_of(Gitlab::Metrics::Transaction)
- .to receive(:add_event).with(:sidekiq_exception)
-
- expect do
- middleware.call(worker, message, :test) do
- ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
- raise RuntimeError
- end
- end.to raise_error(RuntimeError)
-
- expect(message).to include(db_count: 1, db_write_count: 0, db_cached_count: 0)
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index a31686b8061..edcd5b31941 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -18,59 +18,73 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
end
describe '#sql' do
- describe 'without a current transaction' do
- it 'simply returns' do
- expect_any_instance_of(Gitlab::Metrics::Transaction)
- .not_to receive(:increment)
+ shared_examples 'track query in metrics' do
+ before do
+ allow(subscriber).to receive(:current_transaction)
+ .at_least(:once)
+ .and_return(transaction)
+ end
+
+ it 'increments only db count value' do
+ described_class::DB_COUNTERS.each do |counter|
+ prometheus_counter = "gitlab_transaction_#{counter}_total".to_sym
+ if expected_counters[counter] > 0
+ expect(transaction).to receive(:increment).with(prometheus_counter, 1)
+ else
+ expect(transaction).not_to receive(:increment).with(prometheus_counter, 1)
+ end
+ end
subscriber.sql(event)
end
end
- describe 'with a current transaction' do
- shared_examples 'track executed query' do
- before do
- allow(subscriber).to receive(:current_transaction)
- .at_least(:once)
- .and_return(transaction)
- end
+ shared_examples 'track query in RequestStore' do
+ context 'when RequestStore is enabled' do
+ it 'caches db count value', :request_store, :aggregate_failures do
+ subscriber.sql(event)
- it 'increments only db count value' do
described_class::DB_COUNTERS.each do |counter|
- prometheus_counter = "gitlab_transaction_#{counter}_total".to_sym
- if expected_counters[counter] > 0
- expect(transaction).to receive(:increment).with(prometheus_counter, 1)
- else
- expect(transaction).not_to receive(:increment).with(prometheus_counter, 1)
- end
+ expect(Gitlab::SafeRequestStore[counter].to_i).to eq expected_counters[counter]
end
-
- subscriber.sql(event)
end
- context 'when RequestStore is enabled' do
- it 'caches db count value', :request_store, :aggregate_failures do
- subscriber.sql(event)
+ it 'prevents db counters from leaking to the next transaction' do
+ 2.times do
+ Gitlab::WithRequestStore.with_request_store do
+ subscriber.sql(event)
- described_class::DB_COUNTERS.each do |counter|
- expect(Gitlab::SafeRequestStore[counter].to_i).to eq expected_counters[counter]
+ described_class::DB_COUNTERS.each do |counter|
+ expect(Gitlab::SafeRequestStore[counter].to_i).to eq expected_counters[counter]
+ end
end
end
+ end
+ end
+ end
+
+ describe 'without a current transaction' do
+ it 'does not track any metrics' do
+ expect_any_instance_of(Gitlab::Metrics::Transaction)
+ .not_to receive(:increment)
- it 'prevents db counters from leaking to the next transaction' do
- 2.times do
- Gitlab::WithRequestStore.with_request_store do
- subscriber.sql(event)
+ subscriber.sql(event)
+ end
- described_class::DB_COUNTERS.each do |counter|
- expect(Gitlab::SafeRequestStore[counter].to_i).to eq expected_counters[counter]
- end
- end
- end
- end
+ context 'with read query' do
+ let(:expected_counters) do
+ {
+ db_count: 1,
+ db_write_count: 0,
+ db_cached_count: 0
+ }
end
+
+ it_behaves_like 'track query in RequestStore'
end
+ end
+ describe 'with a current transaction' do
it 'observes sql_duration metric' do
expect(subscriber).to receive(:current_transaction)
.at_least(:once)
@@ -96,12 +110,14 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
}
end
- it_behaves_like 'track executed query'
+ it_behaves_like 'track query in metrics'
+ it_behaves_like 'track query in RequestStore'
context 'with only select' do
let(:payload) { { sql: 'WITH active_milestones AS (SELECT COUNT(*), state FROM milestones GROUP BY state) SELECT * FROM active_milestones' } }
- it_behaves_like 'track executed query'
+ it_behaves_like 'track query in metrics'
+ it_behaves_like 'track query in RequestStore'
end
end
@@ -117,33 +133,38 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
context 'with select for update sql event' do
let(:payload) { { sql: 'SELECT * FROM users WHERE id = 10 FOR UPDATE' } }
- it_behaves_like 'track executed query'
+ it_behaves_like 'track query in metrics'
+ it_behaves_like 'track query in RequestStore'
end
context 'with common table expression' do
context 'with insert' do
let(:payload) { { sql: 'WITH archived_rows AS (SELECT * FROM users WHERE archived = true) INSERT INTO products_log SELECT * FROM archived_rows' } }
- it_behaves_like 'track executed query'
+ it_behaves_like 'track query in metrics'
+ it_behaves_like 'track query in RequestStore'
end
end
context 'with delete sql event' do
let(:payload) { { sql: 'DELETE FROM users where id = 10' } }
- it_behaves_like 'track executed query'
+ it_behaves_like 'track query in metrics'
+ it_behaves_like 'track query in RequestStore'
end
context 'with insert sql event' do
let(:payload) { { sql: 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects' } }
- it_behaves_like 'track executed query'
+ it_behaves_like 'track query in metrics'
+ it_behaves_like 'track query in RequestStore'
end
context 'with update sql event' do
let(:payload) { { sql: 'UPDATE users SET admin = true WHERE id = 10' } }
- it_behaves_like 'track executed query'
+ it_behaves_like 'track query in metrics'
+ it_behaves_like 'track query in RequestStore'
end
end
@@ -164,18 +185,20 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
}
end
- it_behaves_like 'track executed query'
+ it_behaves_like 'track query in metrics'
+ it_behaves_like 'track query in RequestStore'
end
context 'with cached payload name' do
let(:payload) do
{
- sql: 'SELECT * FROM users WHERE id = 10',
- name: 'CACHE'
+ sql: 'SELECT * FROM users WHERE id = 10',
+ name: 'CACHE'
}
end
- it_behaves_like 'track executed query'
+ it_behaves_like 'track query in metrics'
+ it_behaves_like 'track query in RequestStore'
end
end
@@ -227,8 +250,8 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
it 'skips schema/begin/commit sql commands' do
allow(subscriber).to receive(:current_transaction)
- .at_least(:once)
- .and_return(transaction)
+ .at_least(:once)
+ .and_return(transaction)
expect(transaction).not_to receive(:increment)
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 88293f11149..d4e5a1a94f2 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -20,14 +20,6 @@ RSpec.describe Gitlab::Metrics::Transaction do
end
end
- describe '#thread_cpu_duration' do
- it 'returns the duration of a transaction in seconds' do
- transaction.run { }
-
- expect(transaction.thread_cpu_duration).to be > 0
- end
- end
-
describe '#run' do
it 'yields the supplied block' do
expect { |b| transaction.run(&b) }.to yield_control
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index 6903ce53f65..6ee9564ef75 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -80,13 +80,15 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
context 'when request goes to Grape endpoint' do
before do
route = double(:route, request_method: 'GET', path: '/:version/projects/:id/archive(.:format)')
- endpoint = double(:endpoint, route: route)
+ endpoint = double(:endpoint, route: route,
+ options: { for: API::Projects, path: [":id/archive"] },
+ namespace: "/projects")
env['api.endpoint'] = endpoint
end
it 'provides labels with the method and path of the route in the grape endpoint' do
- expect(transaction.labels).to eq({ controller: 'Grape', action: 'GET /projects/:id/archive', feature_category: '' })
+ expect(transaction.labels).to eq({ controller: 'Grape', action: 'GET /projects/:id/archive', feature_category: 'projects' })
end
it 'contains only the labels defined for transactions' do
diff --git a/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb b/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb
index 156a440833c..132a0e9ca78 100644
--- a/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb
+++ b/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb
@@ -57,17 +57,45 @@ RSpec.describe Gitlab::Pagination::GitalyKeysetPager do
end
context 'with branch_list_keyset_pagination feature on' do
+ let(:fake_request) { double(url: "#{incoming_api_projects_url}?#{query.to_query}") }
+ let(:branch1) { double 'branch', name: 'branch1' }
+ let(:branch2) { double 'branch', name: 'branch2' }
+ let(:branch3) { double 'branch', name: 'branch3' }
+
before do
stub_feature_flags(branch_list_keyset_pagination: project)
end
context 'without keyset pagination option' do
- it_behaves_like 'offset pagination'
+ context 'when first page is requested' do
+ let(:branches) { [branch1, branch2, branch3] }
+
+ it 'keyset pagination is used with offset headers' do
+ allow(request_context).to receive(:request).and_return(fake_request)
+ allow(project.repository).to receive(:branch_count).and_return(branches.size)
+
+ expect(finder).to receive(:execute).with(gitaly_pagination: true).and_return(branches)
+ expect(request_context).to receive(:header).with('X-Per-Page', '2')
+ expect(request_context).to receive(:header).with('X-Page', '1')
+ expect(request_context).to receive(:header).with('X-Next-Page', '2')
+ expect(request_context).to receive(:header).with('X-Prev-Page', '')
+ expect(request_context).to receive(:header).with('Link', kind_of(String))
+ expect(request_context).to receive(:header).with('X-Total', '3')
+ expect(request_context).to receive(:header).with('X-Total-Pages', '2')
+
+ pager.paginate(finder)
+ end
+ end
+
+ context 'when second page is requested' do
+ let(:base_query) { { per_page: 2, page: 2 } }
+
+ it_behaves_like 'offset pagination'
+ end
end
context 'with keyset pagination option' do
let(:query) { base_query.merge(pagination: 'keyset') }
- let(:fake_request) { double(url: "#{incoming_api_projects_url}?#{query.to_query}") }
before do
allow(request_context).to receive(:request).and_return(fake_request)
@@ -75,8 +103,6 @@ RSpec.describe Gitlab::Pagination::GitalyKeysetPager do
end
context 'when next page could be available' do
- let(:branch1) { double 'branch', name: 'branch1' }
- let(:branch2) { double 'branch', name: 'branch2' }
let(:branches) { [branch1, branch2] }
let(:expected_next_page_link) { %Q(<#{incoming_api_projects_url}?#{query.merge(page_token: branch2.name).to_query}>; rel="next") }
@@ -90,7 +116,6 @@ RSpec.describe Gitlab::Pagination::GitalyKeysetPager do
end
context 'when the current page is the last page' do
- let(:branch1) { double 'branch', name: 'branch1' }
let(:branches) { [branch1] }
it 'uses keyset pagination without link headers' do
diff --git a/spec/lib/gitlab/pagination/offset_header_builder_spec.rb b/spec/lib/gitlab/pagination/offset_header_builder_spec.rb
new file mode 100644
index 00000000000..a415bad5135
--- /dev/null
+++ b/spec/lib/gitlab/pagination/offset_header_builder_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Pagination::OffsetHeaderBuilder do
+ let(:request) { double(url: 'http://localhost') }
+ let(:request_context) { double(header: nil, params: { per_page: 5 }, request: request) }
+
+ subject do
+ described_class.new(
+ request_context: request_context, per_page: 5, page: 2,
+ next_page: 3, prev_page: 1, total: 10, total_pages: 3
+ )
+ end
+
+ describe '#execute' do
+ let(:basic_links) do
+ %{<http://localhost?page=1&per_page=5>; rel="prev", <http://localhost?page=3&per_page=5>; rel="next", <http://localhost?page=1&per_page=5>; rel="first"}
+ end
+
+ let(:last_link) do
+ %{, <http://localhost?page=3&per_page=5>; rel="last"}
+ end
+
+ def expect_basic_headers
+ expect(request_context).to receive(:header).with('X-Per-Page', '5')
+ expect(request_context).to receive(:header).with('X-Page', '2')
+ expect(request_context).to receive(:header).with('X-Next-Page', '3')
+ expect(request_context).to receive(:header).with('X-Prev-Page', '1')
+ expect(request_context).to receive(:header).with('Link', basic_links + last_link)
+ end
+
+ it 'sets headers to request context' do
+ expect_basic_headers
+ expect(request_context).to receive(:header).with('X-Total', '10')
+ expect(request_context).to receive(:header).with('X-Total-Pages', '3')
+
+ subject.execute
+ end
+
+ context 'exclude total headers' do
+ it 'does not set total headers to request context' do
+ expect_basic_headers
+ expect(request_context).not_to receive(:header)
+
+ subject.execute(exclude_total_headers: true)
+ end
+ end
+
+ context 'pass data without counts' do
+ let(:last_link) { '' }
+
+ it 'does not set total headers to request context' do
+ expect_basic_headers
+ expect(request_context).not_to receive(:header)
+
+ subject.execute(data_without_counts: true)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index f320b8a66e8..8e9f7e372c5 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -433,37 +433,85 @@ RSpec.describe Gitlab::PathRegex do
it { is_expected.not_to match('gitlab.git') }
end
- shared_examples 'invalid snippet routes' do
- it { is_expected.not_to match('gitlab-org/gitlab/snippets/1.git') }
- it { is_expected.not_to match('snippets/1.git') }
- it { is_expected.not_to match('gitlab-org/gitlab/snippets/') }
- it { is_expected.not_to match('/gitlab-org/gitlab/snippets/1') }
- it { is_expected.not_to match('gitlab-org/gitlab/snippets/foo') }
- it { is_expected.not_to match('root/snippets/1') }
- it { is_expected.not_to match('/snippets/1') }
- it { is_expected.not_to match('snippets/') }
- it { is_expected.not_to match('snippets/foo') }
- end
+ context 'repository routes' do
+ # Paths that match a known container
+ let_it_be(:container_paths) do
+ [
+ 'gitlab-org',
+ 'gitlab-org/gitlab-test',
+ 'gitlab-org/gitlab-test/snippets/1',
+ 'gitlab-org/gitlab-test/snippets/foo', # ambiguous, we allow creating a sub-group called 'snippets'
+ 'snippets/1'
+ ]
+ end
+
+ # Paths that never match a container
+ let_it_be(:invalid_paths) do
+ [
+ 'gitlab/',
+ '/gitlab',
+ 'gitlab/foo/',
+ '?gitlab',
+ 'git lab',
+ '/snippets/1',
+ 'snippets/foo',
+ 'gitlab-org/gitlab/snippets/'
+ ]
+ end
+
+ let_it_be(:git_paths) { container_paths.map { |path| path + '.git' } }
+ let_it_be(:snippet_paths) { container_paths.grep(%r{snippets/\d}) }
+ let_it_be(:wiki_git_paths) { (container_paths - snippet_paths).map { |path| path + '.wiki.git' } }
+ let_it_be(:invalid_git_paths) { invalid_paths.map { |path| path + '.git' } }
+
+ def expect_route_match(paths)
+ paths.each { |path| is_expected.to match(path) }
+ end
+
+ def expect_no_route_match(paths)
+ paths.each { |path| is_expected.not_to match(path) }
+ end
+
+ describe '.repository_route_regex' do
+ subject { %r{\A#{described_class.repository_route_regex}\z} }
+
+ it 'matches the expected paths' do
+ expect_route_match(container_paths)
+ expect_no_route_match(invalid_paths + git_paths)
+ end
+ end
- describe '.full_snippets_repository_path_regex' do
- subject { described_class.full_snippets_repository_path_regex }
+ describe '.repository_git_route_regex' do
+ subject { %r{\A#{described_class.repository_git_route_regex}\z} }
- it { is_expected.to match('gitlab-org/gitlab/snippets/1') }
- it { is_expected.to match('snippets/1') }
+ it 'matches the expected paths' do
+ expect_route_match(git_paths + wiki_git_paths)
+ expect_no_route_match(container_paths + invalid_paths + invalid_git_paths)
+ end
+ end
- it_behaves_like 'invalid snippet routes'
- end
+ describe '.repository_wiki_git_route_regex' do
+ subject { %r{\A#{described_class.repository_wiki_git_route_regex}\z} }
- describe '.personal_and_project_snippets_path_regex' do
- subject { %r{\A#{described_class.personal_and_project_snippets_path_regex}\z} }
+ it 'matches the expected paths' do
+ expect_route_match(wiki_git_paths)
+ expect_no_route_match(git_paths + invalid_git_paths)
+ end
- it { is_expected.to match('gitlab-org/gitlab/snippets') }
- it { is_expected.to match('snippets') }
+ it { is_expected.not_to match('snippets/1.wiki.git') }
+ end
- it { is_expected.not_to match('gitlab-org/gitlab/snippets/1') }
- it { is_expected.not_to match('snippets/1') }
+ describe '.full_snippets_repository_path_regex' do
+ subject { described_class.full_snippets_repository_path_regex }
- it_behaves_like 'invalid snippet routes'
+ it 'matches the expected paths' do
+ expect_route_match(snippet_paths)
+ expect_no_route_match(container_paths - snippet_paths + git_paths + invalid_paths)
+ end
+
+ it { is_expected.not_to match('root/snippets/1') }
+ it { is_expected.not_to match('gitlab-org/gitlab-test/snippets/foo') }
+ end
end
describe '.container_image_regex' do
diff --git a/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb b/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb
new file mode 100644
index 00000000000..bbc8b0d67e0
--- /dev/null
+++ b/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::PerformanceBar::RedisAdapterWhenPeekEnabled do
+ include ExclusiveLeaseHelpers
+
+ let(:peek_adapter) do
+ Class.new do
+ prepend Gitlab::PerformanceBar::RedisAdapterWhenPeekEnabled
+
+ def initialize(client)
+ @client = client
+ end
+
+ def save(id)
+ # no-op
+ end
+ end
+ end
+
+ describe '#save' do
+ let(:client) { double }
+ let(:uuid) { 'foo' }
+
+ before do
+ allow(Gitlab::PerformanceBar).to receive(:enabled_for_request?).and_return(true)
+ end
+
+ it 'stores request id and enqueues stats job' do
+ expect_to_obtain_exclusive_lease(GitlabPerformanceBarStatsWorker::LEASE_KEY, uuid)
+ expect(GitlabPerformanceBarStatsWorker).to receive(:perform_in).with(GitlabPerformanceBarStatsWorker::WORKER_DELAY, uuid)
+ expect(client).to receive(:sadd).with(GitlabPerformanceBarStatsWorker::STATS_KEY, uuid)
+
+ peek_adapter.new(client).save('foo')
+ end
+
+ context 'when performance_bar_stats is disabled' do
+ before do
+ stub_feature_flags(performance_bar_stats: false)
+ end
+
+ it 'ignores stats processing for the request' do
+ expect(GitlabPerformanceBarStatsWorker).not_to receive(:perform_in)
+ expect(client).not_to receive(:sadd)
+
+ peek_adapter.new(client).save('foo')
+ end
+ end
+
+ context 'when exclusive lease has been already taken' do
+ before do
+ stub_exclusive_lease_taken(GitlabPerformanceBarStatsWorker::LEASE_KEY)
+ end
+
+ it 'stores request id but does not enqueue any job' do
+ expect(GitlabPerformanceBarStatsWorker).not_to receive(:perform_in)
+ expect(client).to receive(:sadd).with(GitlabPerformanceBarStatsWorker::STATS_KEY, uuid)
+
+ peek_adapter.new(client).save('foo')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/performance_bar/stats_spec.rb b/spec/lib/gitlab/performance_bar/stats_spec.rb
new file mode 100644
index 00000000000..c34c6f7b31f
--- /dev/null
+++ b/spec/lib/gitlab/performance_bar/stats_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::PerformanceBar::Stats do
+ describe '#process' do
+ let(:request) { fixture_file('lib/gitlab/performance_bar/peek_data.json') }
+ let(:redis) { double(Gitlab::Redis::SharedState) }
+ let(:logger) { double(Gitlab::PerformanceBar::Logger) }
+ let(:request_id) { 'foo' }
+ let(:stats) { described_class.new(redis) }
+
+ describe '#process' do
+ subject(:process) { stats.process(request_id) }
+
+ before do
+ allow(stats).to receive(:logger).and_return(logger)
+ end
+
+ it 'logs each SQL query including its duration' do
+ allow(redis).to receive(:get).and_return(request)
+
+ expect(logger).to receive(:info)
+ .with({ duration_ms: 1.096, filename: 'lib/gitlab/pagination/offset_pagination.rb',
+ filenum: 53, method: 'add_pagination_headers', request_id: 'foo', type: :sql })
+ expect(logger).to receive(:info)
+ .with({ duration_ms: 0.817, filename: 'lib/api/helpers.rb',
+ filenum: 112, method: 'find_project', request_id: 'foo', type: :sql }).twice
+
+ subject
+ end
+
+ it 'logs an error when the request could not be processed' do
+ allow(redis).to receive(:get).and_return(nil)
+
+ expect(logger).to receive(:error).with(message: anything)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/rack_attack/user_allowlist_spec.rb b/spec/lib/gitlab/rack_attack/user_allowlist_spec.rb
new file mode 100644
index 00000000000..aa604dfab71
--- /dev/null
+++ b/spec/lib/gitlab/rack_attack/user_allowlist_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::RackAttack::UserAllowlist do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(input)}
+
+ where(:input, :elements) do
+ nil | []
+ '' | []
+ '123' | [123]
+ '123,456' | [123, 456]
+ '123,foobar, 456,' | [123, 456]
+ end
+
+ with_them do
+ it 'has the expected elements' do
+ expect(subject).to contain_exactly(*elements)
+ end
+
+ it 'implements empty?' do
+ expect(subject.empty?).to eq(elements.empty?)
+ end
+
+ it 'implements include?' do
+ unless elements.empty?
+ expect(subject).to include(elements.first)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/rack_attack_spec.rb b/spec/lib/gitlab/rack_attack_spec.rb
new file mode 100644
index 00000000000..d72863b0103
--- /dev/null
+++ b/spec/lib/gitlab/rack_attack_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::RackAttack, :aggregate_failures do
+ describe '.configure' do
+ let(:fake_rack_attack) { class_double("Rack::Attack") }
+ let(:fake_rack_attack_request) { class_double("Rack::Attack::Request") }
+
+ let(:throttles) do
+ {
+ throttle_unauthenticated: Gitlab::Throttle.unauthenticated_options,
+ throttle_authenticated_api: Gitlab::Throttle.authenticated_api_options,
+ throttle_product_analytics_collector: { limit: 100, period: 60 },
+ throttle_unauthenticated_protected_paths: Gitlab::Throttle.unauthenticated_options,
+ throttle_authenticated_protected_paths_api: Gitlab::Throttle.authenticated_api_options,
+ throttle_authenticated_protected_paths_web: Gitlab::Throttle.authenticated_web_options
+ }
+ end
+
+ before do
+ stub_const("Rack::Attack", fake_rack_attack)
+ stub_const("Rack::Attack::Request", fake_rack_attack_request)
+
+ # Expect rather than just allow, because this is actually fairly important functionality
+ expect(fake_rack_attack).to receive(:throttled_response_retry_after_header=).with(true)
+ allow(fake_rack_attack).to receive(:throttle)
+ allow(fake_rack_attack).to receive(:track)
+ allow(fake_rack_attack).to receive(:safelist)
+ allow(fake_rack_attack).to receive(:blocklist)
+ end
+
+ it 'extends the request class' do
+ described_class.configure(fake_rack_attack)
+
+ expect(fake_rack_attack_request).to include(described_class::Request)
+ end
+
+ it 'configures the safelist' do
+ described_class.configure(fake_rack_attack)
+
+ expect(fake_rack_attack).to have_received(:safelist).with('throttle_bypass_header')
+ end
+
+ it 'configures throttles if no dry-run was configured' do
+ described_class.configure(fake_rack_attack)
+
+ throttles.each do |throttle, options|
+ expect(fake_rack_attack).to have_received(:throttle).with(throttle.to_s, options)
+ end
+ end
+
+ it 'configures tracks if dry-run was configured for all throttles' do
+ stub_env('GITLAB_THROTTLE_DRY_RUN', '*')
+
+ described_class.configure(fake_rack_attack)
+
+ throttles.each do |throttle, options|
+ expect(fake_rack_attack).to have_received(:track).with(throttle.to_s, options)
+ end
+ expect(fake_rack_attack).not_to have_received(:throttle)
+ end
+
+ it 'configures tracks and throttles with a selected set of dry-runs' do
+ dry_run_throttles = throttles.each_key.first(2)
+ regular_throttles = throttles.keys[2..-1]
+ stub_env('GITLAB_THROTTLE_DRY_RUN', dry_run_throttles.join(','))
+
+ described_class.configure(fake_rack_attack)
+
+ dry_run_throttles.each do |throttle|
+ expect(fake_rack_attack).to have_received(:track).with(throttle.to_s, throttles[throttle])
+ end
+ regular_throttles.each do |throttle|
+ expect(fake_rack_attack).to have_received(:throttle).with(throttle.to_s, throttles[throttle])
+ end
+ end
+
+ context 'user allowlist' do
+ subject { described_class.user_allowlist }
+
+ it 'is empty' do
+ described_class.configure(fake_rack_attack)
+
+ expect(subject).to be_empty
+ end
+
+ it 'reflects GITLAB_THROTTLE_USER_ALLOWLIST' do
+ stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', '123,456')
+ described_class.configure(fake_rack_attack)
+
+ expect(subject).to contain_exactly(123, 456)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sample_data_template_spec.rb b/spec/lib/gitlab/sample_data_template_spec.rb
index 7d0d415b3af..09ca41fcfc2 100644
--- a/spec/lib/gitlab/sample_data_template_spec.rb
+++ b/spec/lib/gitlab/sample_data_template_spec.rb
@@ -6,8 +6,7 @@ RSpec.describe Gitlab::SampleDataTemplate do
describe '.all' do
it 'returns all templates' do
expected = %w[
- basic
- serenity_valley
+ sample
]
expect(described_class.all).to be_an(Array)
@@ -19,7 +18,7 @@ RSpec.describe Gitlab::SampleDataTemplate do
subject { described_class.find(query) }
context 'when there is a match' do
- let(:query) { :basic }
+ let(:query) { :sample }
it { is_expected.to be_a(described_class) }
end
diff --git a/spec/lib/gitlab/setup_helper/workhorse_spec.rb b/spec/lib/gitlab/setup_helper/workhorse_spec.rb
new file mode 100644
index 00000000000..aa9b4595799
--- /dev/null
+++ b/spec/lib/gitlab/setup_helper/workhorse_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SetupHelper::Workhorse do
+ describe '.make' do
+ subject { described_class.make }
+
+ context 'when there is a gmake' do
+ it 'returns gmake' do
+ expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
+
+ expect(subject).to eq 'gmake'
+ end
+ end
+
+ context 'when there is no gmake' do
+ it 'returns make' do
+ expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 1])
+
+ expect(subject).to eq 'make'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_cluster_spec.rb b/spec/lib/gitlab/sidekiq_cluster_spec.rb
index 5517abe1010..3c6ea054968 100644
--- a/spec/lib/gitlab/sidekiq_cluster_spec.rb
+++ b/spec/lib/gitlab/sidekiq_cluster_spec.rb
@@ -123,6 +123,14 @@ RSpec.describe Gitlab::SidekiqCluster do
end
end
+ describe '.count_by_queue' do
+ it 'tallies the queue counts' do
+ queues = [%w(foo), %w(bar baz), %w(foo)]
+
+ expect(described_class.count_by_queue(queues)).to eq(%w(foo) => 2, %w(bar baz) => 1)
+ end
+ end
+
describe '.concurrency' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/sidekiq_death_handler_spec.rb b/spec/lib/gitlab/sidekiq_death_handler_spec.rb
new file mode 100644
index 00000000000..96fef88de4e
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_death_handler_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqDeathHandler, :clean_gitlab_redis_queues do
+ describe '.handler' do
+ context 'when the job class has worker attributes' do
+ let(:test_worker) do
+ Class.new do
+ include WorkerAttributes
+
+ urgency :low
+ worker_has_external_dependencies!
+ worker_resource_boundary :cpu
+ feature_category :users
+ end
+ end
+
+ before do
+ stub_const('TestWorker', test_worker)
+ end
+
+ it 'uses the attributes from the worker' do
+ expect(described_class.counter)
+ .to receive(:increment)
+ .with(queue: 'test_queue', worker: 'TestWorker',
+ urgency: 'low', external_dependencies: 'yes',
+ feature_category: 'users', boundary: 'cpu')
+
+ described_class.handler({ 'class' => 'TestWorker', 'queue' => 'test_queue' }, nil)
+ end
+ end
+
+ context 'when the job class does not have worker attributes' do
+ before do
+ stub_const('TestWorker', Class.new)
+ end
+
+ it 'uses blank attributes' do
+ expect(described_class.counter)
+ .to receive(:increment)
+ .with(queue: 'test_queue', worker: 'TestWorker',
+ urgency: '', external_dependencies: 'no',
+ feature_category: '', boundary: '')
+
+ described_class.handler({ 'class' => 'TestWorker', 'queue' => 'test_queue' }, nil)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
index 8ef61d4eae9..0285467ecab 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
@@ -131,31 +131,6 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gi
end
end
- describe '#droppable?' do
- where(:idempotent, :prevent_deduplication) do
- # [true, false].repeated_permutation(2)
- [[true, true],
- [true, false],
- [false, true],
- [false, false]]
- end
-
- with_them do
- before do
- allow(AuthorizedProjectsWorker).to receive(:idempotent?).and_return(idempotent)
- stub_feature_flags("disable_#{queue}_deduplication" => prevent_deduplication)
- end
-
- it 'is droppable when all conditions are met' do
- if idempotent && !prevent_deduplication
- expect(duplicate_job).to be_droppable
- else
- expect(duplicate_job).not_to be_droppable
- end
- end
- end
- end
-
describe '#scheduled_at' do
let(:scheduled_at) { 42 }
let(:job) do
@@ -181,6 +156,46 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gi
end
end
+ describe '#idempotent?' do
+ context 'when worker class does not exist' do
+ let(:job) { { 'class' => '' } }
+
+ it 'returns false' do
+ expect(duplicate_job).not_to be_idempotent
+ end
+ end
+
+ context 'when worker class does not respond to #idempotent?' do
+ before do
+ stub_const('AuthorizedProjectsWorker', Class.new)
+ end
+
+ it 'returns false' do
+ expect(duplicate_job).not_to be_idempotent
+ end
+ end
+
+ context 'when worker class is not idempotent' do
+ before do
+ allow(AuthorizedProjectsWorker).to receive(:idempotent?).and_return(false)
+ end
+
+ it 'returns false' do
+ expect(duplicate_job).not_to be_idempotent
+ end
+ end
+
+ context 'when worker class is idempotent' do
+ before do
+ allow(AuthorizedProjectsWorker).to receive(:idempotent?).and_return(true)
+ end
+
+ it 'returns true' do
+ expect(duplicate_job).to be_idempotent
+ end
+ end
+ end
+
def set_idempotency_key(key, value = '1')
Sidekiq.redis { |r| r.set(key, value) }
end
diff --git a/spec/lib/gitlab/tracking/destinations/product_analytics_spec.rb b/spec/lib/gitlab/tracking/destinations/product_analytics_spec.rb
new file mode 100644
index 00000000000..63e2e930acd
--- /dev/null
+++ b/spec/lib/gitlab/tracking/destinations/product_analytics_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Tracking::Destinations::ProductAnalytics do
+ let(:emitter) { SnowplowTracker::Emitter.new('localhost', buffer_size: 1) }
+ let(:tracker) { SnowplowTracker::Tracker.new(emitter, SnowplowTracker::Subject.new, 'namespace', 'app_id') }
+
+ describe '#event' do
+ shared_examples 'does not send an event' do
+ it 'does not send an event' do
+ expect_any_instance_of(SnowplowTracker::Tracker).not_to receive(:track_struct_event)
+
+ subject.event(allowed_category, allowed_action)
+ end
+ end
+
+ let(:allowed_category) { 'epics' }
+ let(:allowed_action) { 'promote' }
+ let(:self_monitoring_project) { create(:project) }
+
+ before do
+ stub_feature_flags(product_analytics_tracking: true)
+ stub_application_setting(self_monitoring_project_id: self_monitoring_project.id)
+ stub_application_setting(usage_ping_enabled: true)
+ end
+
+ context 'with allowed event' do
+ it 'sends an event to Product Analytics snowplow collector' do
+ expect(SnowplowTracker::AsyncEmitter)
+ .to receive(:new)
+ .with(ProductAnalytics::Tracker::COLLECTOR_URL, protocol: Gitlab.config.gitlab.protocol)
+ .and_return(emitter)
+
+ expect(SnowplowTracker::Tracker)
+ .to receive(:new)
+ .with(emitter, an_instance_of(SnowplowTracker::Subject), Gitlab::Tracking::SNOWPLOW_NAMESPACE, self_monitoring_project.id.to_s)
+ .and_return(tracker)
+
+ freeze_time do
+ expect(tracker)
+ .to receive(:track_struct_event)
+ .with(allowed_category, allowed_action, 'label', 'property', 1.5, nil, (Time.now.to_f * 1000).to_i)
+
+ subject.event(allowed_category, allowed_action, label: 'label', property: 'property', value: 1.5)
+ end
+ end
+ end
+
+ context 'with non-allowed event' do
+ it 'does not send an event' do
+ expect_any_instance_of(SnowplowTracker::Tracker).not_to receive(:track_struct_event)
+
+ subject.event('category', 'action')
+ subject.event(allowed_category, 'action')
+ subject.event('category', allowed_action)
+ end
+ end
+
+ context 'when self-monitoring project does not exist' do
+ before do
+ stub_application_setting(self_monitoring_project_id: nil)
+ end
+
+ include_examples 'does not send an event'
+ end
+
+ context 'when product_analytics_tracking FF is disabled' do
+ before do
+ stub_feature_flags(product_analytics_tracking: false)
+ end
+
+ include_examples 'does not send an event'
+ end
+
+ context 'when usage ping is disabled' do
+ before do
+ stub_application_setting(usage_ping_enabled: false)
+ end
+
+ include_examples 'does not send an event'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb b/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
index ee63eb6de04..0e8647ad78a 100644
--- a/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
+++ b/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow do
it 'sends event to tracker' do
allow(tracker).to receive(:track_self_describing_event).and_call_original
- subject.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', foo: 'bar')
+ subject.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' })
expect(tracker).to have_received(:track_self_describing_event) do |event, context, timestamp|
expect(event.to_json[:schema]).to eq('iglu:com.gitlab/foo/jsonschema/1-0-0')
@@ -71,7 +71,7 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow do
it 'does not send event to tracker' do
expect_any_instance_of(SnowplowTracker::Tracker).not_to receive(:track_self_describing_event)
- subject.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', foo: 'bar')
+ subject.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' })
end
end
end
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index 805bd92fd43..57882de0974 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -36,6 +36,11 @@ RSpec.describe Gitlab::Tracking do
end
describe '.event' do
+ before do
+ allow_any_instance_of(Gitlab::Tracking::Destinations::Snowplow).to receive(:event)
+ allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event)
+ end
+
it 'delegates to snowplow destination' do
expect_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
.to receive(:event)
@@ -43,15 +48,23 @@ RSpec.describe Gitlab::Tracking do
described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5)
end
+
+ it 'delegates to ProductAnalytics destination' do
+ expect_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics)
+ .to receive(:event)
+ .with('category', 'action', label: 'label', property: 'property', value: 1.5, context: nil)
+
+ described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5)
+ end
end
describe '.self_describing_event' do
it 'delegates to snowplow destination' do
expect_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
.to receive(:self_describing_event)
- .with('iglu:com.gitlab/foo/jsonschema/1-0-0', { foo: 'bar' }, context: nil)
+ .with('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' }, context: nil)
- described_class.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', foo: 'bar')
+ described_class.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' })
end
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb b/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb
index e9fb5346eae..c0deb2aa00c 100644
--- a/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'aggregated metrics' do
Gitlab::UsageDataCounters::HLLRedisCounter.known_event?(event)
end
- failure_message do
+ failure_message do |event|
"Event with name: `#{event}` can not be found within `#{Gitlab::UsageDataCounters::HLLRedisCounter::KNOWN_EVENTS_PATH}`"
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/base_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/base_counter_spec.rb
new file mode 100644
index 00000000000..4a31191d75f
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/base_counter_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::BaseCounter do
+ describe '.fetch_supported_event' do
+ subject { described_class.fetch_supported_event(event_name) }
+
+ let(:event_name) { 'generic_event' }
+ let(:prefix) { 'generic' }
+ let(:known_events) { %w[event another_event] }
+
+ before do
+ allow(described_class).to receive(:prefix) { prefix }
+ allow(described_class).to receive(:known_events) { known_events }
+ end
+
+ it 'returns the matching event' do
+ is_expected.to eq 'event'
+ end
+
+ context 'when event is unknown' do
+ let(:event_name) { 'generic_unknown_event' }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when prefix does not match the event name' do
+ let(:prefix) { 'special' }
+
+ it { is_expected.to be_nil }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
index f2c1d8718d7..82db3d94493 100644
--- a/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
@@ -74,7 +74,19 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
end
end
- it 'can return the count of actions per user deduplicated ' do
+ context 'for SSE edit actions' do
+ it_behaves_like 'tracks and counts action' do
+ def track_action(params)
+ described_class.track_sse_edit_action(**params)
+ end
+
+ def count_unique(params)
+ described_class.count_sse_edit_actions(**params)
+ end
+ end
+ end
+
+ it 'can return the count of actions per user deduplicated' do
described_class.track_web_ide_edit_action(author: user1)
described_class.track_snippet_editor_edit_action(author: user1)
described_class.track_sfe_edit_action(author: user1)
diff --git a/spec/lib/gitlab/usage_data_counters/guest_package_event_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/guest_package_event_counter_spec.rb
new file mode 100644
index 00000000000..d018100b041
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/guest_package_event_counter_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::GuestPackageEventCounter, :clean_gitlab_redis_shared_state do
+ shared_examples_for 'usage counter with totals' do |counter|
+ it 'increments counter and returns total count' do
+ expect(described_class.read(counter)).to eq(0)
+
+ 2.times { described_class.count(counter) }
+
+ expect(described_class.read(counter)).to eq(2)
+ end
+ end
+
+ it 'includes the right events' do
+ expect(described_class::KNOWN_EVENTS.size).to eq 33
+ end
+
+ described_class::KNOWN_EVENTS.each do |event|
+ it_behaves_like 'usage counter with totals', event
+ end
+
+ describe '.fetch_supported_event' do
+ subject { described_class.fetch_supported_event(event_name) }
+
+ let(:event_name) { 'package_guest_i_package_composer_guest_push' }
+
+ it { is_expected.to eq 'i_package_composer_guest_push' }
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
index 93704a39555..70ee9871486 100644
--- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
@@ -30,6 +30,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'search',
'source_code',
'incident_management',
+ 'incident_management_alerts',
'testing',
'issues_edit',
'ci_secrets_management',
@@ -43,7 +44,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'golang_packages',
'debian_packages',
'container_packages',
- 'tag_packages'
+ 'tag_packages',
+ 'snippets'
)
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
index 803eff05efe..bf43f7552e6 100644
--- a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
@@ -118,6 +118,16 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
end
+ context 'for Issue cloned actions' do
+ it_behaves_like 'a tracked issue edit event' do
+ let(:action) { described_class::ISSUE_CLONED }
+
+ def track_action(params)
+ described_class.track_issue_cloned_action(**params)
+ end
+ end
+ end
+
context 'for Issue relate actions' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_RELATED }
diff --git a/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
index b55e20ba555..17188a75ccb 100644
--- a/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
@@ -20,4 +20,12 @@ RSpec.describe Gitlab::UsageDataCounters::SearchCounter, :clean_gitlab_redis_sha
context 'navbar_searches counter' do
it_behaves_like 'usage counter with totals', :navbar_searches
end
+
+ describe '.fetch_supported_event' do
+ subject { described_class.fetch_supported_event(event_name) }
+
+ let(:event_name) { 'all_searches' }
+
+ it { is_expected.to eq 'all_searches' }
+ end
end
diff --git a/spec/lib/gitlab/usage_data_counters_spec.rb b/spec/lib/gitlab/usage_data_counters_spec.rb
new file mode 100644
index 00000000000..379a2cb778d
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters do
+ describe '.usage_data_counters' do
+ subject { described_class.counters }
+
+ it { is_expected.to all(respond_to :totals) }
+ it { is_expected.to all(respond_to :fallback_totals) }
+ end
+
+ describe '.count' do
+ subject { described_class.count(event_name) }
+
+ let(:event_name) { 'static_site_editor_views' }
+
+ it 'increases a view counter' do
+ expect(Gitlab::UsageDataCounters::StaticSiteEditorCounter).to receive(:count).with('views')
+
+ subject
+ end
+
+ context 'when event_name is not defined' do
+ let(:event_name) { 'unknown' }
+
+ it 'raises an exception' do
+ expect { subject }.to raise_error(Gitlab::UsageDataCounters::UnknownEvent)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index d305b2c5bfe..c2d96369425 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -224,7 +224,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
gitlab: 2
},
projects_imported: {
- total: 20,
+ total: 2,
gitlab_project: 2,
gitlab: 2,
github: 2,
@@ -248,7 +248,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
gitlab: 1
},
projects_imported: {
- total: 10,
+ total: 1,
gitlab_project: 1,
gitlab: 1,
github: 1,
@@ -456,6 +456,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(count_data[:projects]).to eq(4)
expect(count_data[:projects_asana_active]).to eq(0)
expect(count_data[:projects_prometheus_active]).to eq(1)
+ expect(count_data[:projects_jenkins_active]).to eq(1)
expect(count_data[:projects_jira_active]).to eq(4)
expect(count_data[:projects_jira_server_active]).to eq(2)
expect(count_data[:projects_jira_cloud_active]).to eq(2)
@@ -653,6 +654,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
it { is_expected.to include(:kubernetes_agent_gitops_sync) }
it { is_expected.to include(:static_site_editor_views) }
+ it { is_expected.to include(:package_guest_i_package_composer_guest_pull) }
end
describe '.usage_data_counters' do
@@ -840,24 +842,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '.cycle_analytics_usage_data' do
- subject { described_class.cycle_analytics_usage_data }
-
- it 'works when queries time out in new' do
- allow(Gitlab::CycleAnalytics::UsageData)
- .to receive(:new).and_raise(ActiveRecord::StatementInvalid.new(''))
-
- expect { subject }.not_to raise_error
- end
-
- it 'works when queries time out in to_json' do
- allow_any_instance_of(Gitlab::CycleAnalytics::UsageData)
- .to receive(:to_json).and_raise(ActiveRecord::StatementInvalid.new(''))
-
- expect { subject }.not_to raise_error
- end
- end
-
describe '.ingress_modsecurity_usage' do
subject { described_class.ingress_modsecurity_usage }
@@ -1054,6 +1038,14 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
end
+
+ describe ".system_usage_data_settings" do
+ subject { described_class.system_usage_data_settings }
+
+ it 'gathers settings usage data', :aggregate_failures do
+ expect(subject[:settings][:ldap_encrypted_secrets_enabled]).to eq(Gitlab::Auth::Ldap::Config.encrypted_secrets.active?)
+ end
+ end
end
describe '.merge_requests_users', :clean_gitlab_redis_shared_state do
@@ -1122,6 +1114,12 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
counter.track_web_ide_edit_action(author: user3, time: time - 3.days)
counter.track_snippet_editor_edit_action(author: user3)
+
+ counter.track_sse_edit_action(author: user1)
+ counter.track_sse_edit_action(author: user1)
+ counter.track_sse_edit_action(author: user2)
+ counter.track_sse_edit_action(author: user3)
+ counter.track_sse_edit_action(author: user2, time: time - 3.days)
end
it 'returns the distinct count of user actions within the specified time period' do
@@ -1134,7 +1132,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
action_monthly_active_users_web_ide_edit: 2,
action_monthly_active_users_sfe_edit: 2,
action_monthly_active_users_snippet_editor_edit: 2,
- action_monthly_active_users_ide_edit: 3
+ action_monthly_active_users_ide_edit: 3,
+ action_monthly_active_users_sse_edit: 3
}
)
end
@@ -1235,7 +1234,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
subject { described_class.redis_hll_counters }
let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories }
- let(:ineligible_total_categories) { %w[source_code testing ci_secrets_management] }
+ let(:ineligible_total_categories) { %w[source_code testing ci_secrets_management incident_management_alerts snippets] }
it 'has all known_events' do
expect(subject).to have_key(:redis_hll_counters)
@@ -1256,45 +1255,21 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe 'aggregated_metrics' do
- shared_examples 'aggregated_metrics_for_time_range' do
- context 'with product_analytics_aggregated_metrics feature flag on' do
- before do
- stub_feature_flags(product_analytics_aggregated_metrics: true)
- end
+ describe '.aggregated_metrics_weekly' do
+ subject(:aggregated_metrics_payload) { described_class.aggregated_metrics_weekly }
- it 'uses ::Gitlab::UsageDataCounters::HLLRedisCounter#aggregated_metrics_data', :aggregate_failures do
- expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(aggregated_metrics_data_method).and_return(global_search_gmau: 123)
- expect(aggregated_metrics_payload).to eq(aggregated_metrics: { global_search_gmau: 123 })
- end
- end
-
- context 'with product_analytics_aggregated_metrics feature flag off' do
- before do
- stub_feature_flags(product_analytics_aggregated_metrics: false)
- end
-
- it 'returns empty hash', :aggregate_failures do
- expect(::Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(aggregated_metrics_data_method)
- expect(aggregated_metrics_payload).to be {}
- end
- end
+ it 'uses ::Gitlab::UsageDataCounters::HLLRedisCounter#aggregated_metrics_data', :aggregate_failures do
+ expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:aggregated_metrics_weekly_data).and_return(global_search_gmau: 123)
+ expect(aggregated_metrics_payload).to eq(aggregated_metrics: { global_search_gmau: 123 })
end
+ end
- describe '.aggregated_metrics_weekly' do
- subject(:aggregated_metrics_payload) { described_class.aggregated_metrics_weekly }
-
- let(:aggregated_metrics_data_method) { :aggregated_metrics_weekly_data }
-
- it_behaves_like 'aggregated_metrics_for_time_range'
- end
-
- describe '.aggregated_metrics_monthly' do
- subject(:aggregated_metrics_payload) { described_class.aggregated_metrics_monthly }
-
- let(:aggregated_metrics_data_method) { :aggregated_metrics_monthly_data }
+ describe '.aggregated_metrics_monthly' do
+ subject(:aggregated_metrics_payload) { described_class.aggregated_metrics_monthly }
- it_behaves_like 'aggregated_metrics_for_time_range'
+ it 'uses ::Gitlab::UsageDataCounters::HLLRedisCounter#aggregated_metrics_data', :aggregate_failures do
+ expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:aggregated_metrics_monthly_data).and_return(global_search_gmau: 123)
+ expect(aggregated_metrics_payload).to eq(aggregated_metrics: { global_search_gmau: 123 })
end
end
@@ -1323,7 +1298,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
context 'and product_analytics FF is enabled for it' do
before do
- stub_feature_flags(product_analytics: project)
+ stub_feature_flags(product_analytics_tracking: true)
create(:product_analytics_event, project: project, se_category: 'epics', se_action: 'promote')
create(:product_analytics_event, project: project, se_category: 'epics', se_action: 'promote', collector_tstamp: 2.days.ago)
@@ -1339,7 +1314,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
context 'and product_analytics FF is disabled' do
before do
- stub_feature_flags(product_analytics: false)
+ stub_feature_flags(product_analytics_tracking: false)
end
it 'returns an empty hash' do
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index d6b1e3b2d4b..748a8336a25 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -310,4 +310,24 @@ RSpec.describe Gitlab::UserAccess do
end
end
end
+
+ describe '#can_push_for_ref?' do
+ let(:ref) { 'test_ref' }
+
+ context 'when user cannot push_code to a project repository (eg. as a guest)' do
+ it 'is false' do
+ project.add_user(user, :guest)
+
+ expect(access.can_push_for_ref?(ref)).to be_falsey
+ end
+ end
+
+ context 'when user can push_code to a project repository (eg. as a developer)' do
+ it 'is true' do
+ project.add_user(user, :developer)
+
+ expect(access.can_push_for_ref?(ref)).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb
index 9c0dc69ccd1..521d6584a20 100644
--- a/spec/lib/gitlab/utils/usage_data_spec.rb
+++ b/spec/lib/gitlab/utils/usage_data_spec.rb
@@ -37,6 +37,36 @@ RSpec.describe Gitlab::Utils::UsageData do
end
end
+ describe '#estimate_batch_distinct_count' do
+ let(:relation) { double(:relation) }
+
+ it 'delegates counting to counter class instance' do
+ expect_next_instance_of(Gitlab::Database::PostgresHll::BatchDistinctCounter, relation, 'column') do |instance|
+ expect(instance).to receive(:estimate_distinct_count)
+ .with(batch_size: nil, start: nil, finish: nil)
+ .and_return(5)
+ end
+
+ expect(described_class.estimate_batch_distinct_count(relation, 'column')).to eq(5)
+ end
+
+ it 'returns default fallback value when counting fails due to database error' do
+ stub_const("Gitlab::Utils::UsageData::FALLBACK", 15)
+ allow(Gitlab::Database::PostgresHll::BatchDistinctCounter).to receive(:new).and_raise(ActiveRecord::StatementInvalid.new(''))
+
+ expect(described_class.estimate_batch_distinct_count(relation)).to eq(15)
+ end
+
+ it 'logs error and returns DISTRIBUTED_HLL_FALLBACK value when counting raises any error', :aggregate_failures do
+ error = StandardError.new('')
+ stub_const("Gitlab::Utils::UsageData::DISTRIBUTED_HLL_FALLBACK", 15)
+ allow(Gitlab::Database::PostgresHll::BatchDistinctCounter).to receive(:new).and_raise(error)
+
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(error)
+ expect(described_class.estimate_batch_distinct_count(relation)).to eq(15)
+ end
+ end
+
describe '#sum' do
let(:relation) { double(:relation) }
diff --git a/spec/lib/gitlab/uuid_spec.rb b/spec/lib/gitlab/uuid_spec.rb
new file mode 100644
index 00000000000..a2e28f5a24d
--- /dev/null
+++ b/spec/lib/gitlab/uuid_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UUID do
+ let_it_be(:name) { "GitLab" }
+
+ describe '.v5' do
+ subject { described_class.v5(name) }
+
+ before do
+ # This is necessary to clear memoization for testing different environments
+ described_class.instance_variable_set(:@default_namespace_id, nil)
+ end
+
+ context 'in development' do
+ let_it_be(:development_proper_uuid) { "5b593e54-90f5-504b-8805-5394a4d14b94" }
+
+ before do
+ allow(Rails).to receive(:env).and_return(:development)
+ end
+
+ it { is_expected.to eq(development_proper_uuid) }
+ end
+
+ context 'in test' do
+ let_it_be(:test_proper_uuid) { "5b593e54-90f5-504b-8805-5394a4d14b94" }
+
+ it { is_expected.to eq(test_proper_uuid) }
+ end
+
+ context 'in staging' do
+ let_it_be(:staging_proper_uuid) { "dd190b37-7754-5c7c-80a0-85621a5823ad" }
+
+ before do
+ allow(Rails).to receive(:env).and_return(:staging)
+ end
+
+ it { is_expected.to eq(staging_proper_uuid) }
+ end
+
+ context 'in production' do
+ let_it_be(:production_proper_uuid) { "4961388b-9d8e-5da0-a499-3ef5da58daf0" }
+
+ before do
+ allow(Rails).to receive(:env).and_return(:production)
+ end
+
+ it { is_expected.to eq(production_proper_uuid) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/webpack/manifest_spec.rb b/spec/lib/gitlab/webpack/manifest_spec.rb
index 1427bdd7d4f..08b4774dd67 100644
--- a/spec/lib/gitlab/webpack/manifest_spec.rb
+++ b/spec/lib/gitlab/webpack/manifest_spec.rb
@@ -97,7 +97,7 @@ RSpec.describe Gitlab::Webpack::Manifest do
context "with dev server disabled" do
before do
allow(Gitlab.config.webpack.dev_server).to receive(:enabled).and_return(false)
- allow(File).to receive(:read).with(::Rails.root.join("manifest_output/my_manifest.json")).and_return(manifest)
+ stub_file_read(::Rails.root.join("manifest_output/my_manifest.json"), content: manifest)
end
describe ".asset_paths" do
@@ -105,7 +105,7 @@ RSpec.describe Gitlab::Webpack::Manifest do
it "errors if we can't find the manifest" do
allow(Gitlab.config.webpack).to receive(:manifest_filename).and_return('broken.json')
- allow(File).to receive(:read).with(::Rails.root.join("manifest_output/broken.json")).and_raise(Errno::ENOENT)
+ stub_file_read(::Rails.root.join("manifest_output/broken.json"), error: Errno::ENOENT)
expect { Gitlab::Webpack::Manifest.asset_paths("entry1") }.to raise_error(Gitlab::Webpack::Manifest::ManifestLoadError)
end
end
diff --git a/spec/lib/gitlab_danger_spec.rb b/spec/lib/gitlab_danger_spec.rb
index e332647cf8a..ed668c52a0e 100644
--- a/spec/lib/gitlab_danger_spec.rb
+++ b/spec/lib/gitlab_danger_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
RSpec.describe GitlabDanger do
let(:gitlab_danger_helper) { nil }
@@ -9,7 +9,7 @@ RSpec.describe GitlabDanger do
describe '.local_warning_message' do
it 'returns an informational message with rules that can run' do
- expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changes_size, documentation, frozen_string, duplicate_yarn_dependencies, prettier, eslint, karma, database, commit_messages, product_analytics, utility_css, pajamas')
+ expect(described_class.local_warning_message).to eq("==> Only the following Danger rules can be run locally: #{described_class::LOCAL_RULES.join(', ')}")
end
end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index 7c2758bf27e..e1b8323eb8e 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -26,18 +26,17 @@ RSpec.describe Gitlab do
end
it 'returns the actual Git revision' do
- expect(File).to receive(:read)
- .with(described_class.root.join('REVISION'))
- .and_return("abc123\n")
+ expect_file_read(described_class.root.join('REVISION'), content: "abc123\n")
expect(described_class.revision).to eq('abc123')
end
it 'memoizes the revision' do
+ stub_file_read(described_class.root.join('REVISION'), content: "abc123\n")
+
expect(File).to receive(:read)
- .once
- .with(described_class.root.join('REVISION'))
- .and_return("abc123\n")
+ .once
+ .with(described_class.root.join('REVISION'))
2.times { described_class.revision }
end
@@ -330,4 +329,24 @@ RSpec.describe Gitlab do
expect(described_class.http_proxy_env?).to eq(false)
end
end
+
+ describe '.maintenance_mode?' do
+ it 'returns true when maintenance mode is enabled' do
+ stub_application_setting(maintenance_mode: true)
+
+ expect(described_class.maintenance_mode?).to eq(true)
+ end
+
+ it 'returns false when maintenance mode is disabled' do
+ stub_application_setting(maintenance_mode: false)
+
+ expect(described_class.maintenance_mode?).to eq(false)
+ end
+
+ it 'returns false when maintenance mode feature flag is disabled' do
+ stub_feature_flags(maintenance_mode: false)
+
+ expect(described_class.maintenance_mode?).to eq(false)
+ end
+ end
end
diff --git a/spec/lib/microsoft_teams/notifier_spec.rb b/spec/lib/microsoft_teams/notifier_spec.rb
index c35d7e8420c..3b7892334dd 100644
--- a/spec/lib/microsoft_teams/notifier_spec.rb
+++ b/spec/lib/microsoft_teams/notifier_spec.rb
@@ -51,11 +51,11 @@ RSpec.describe MicrosoftTeams::Notifier do
describe '#body' do
it 'returns Markdown-based body when HTML was passed' do
- expect(subject.send(:body, options)).to eq(body.to_json)
+ expect(subject.send(:body, **options)).to eq(body.to_json)
end
it 'fails when empty Hash was passed' do
- expect { subject.send(:body, {}) }.to raise_error(ArgumentError)
+ expect { subject.send(:body, **{}) }.to raise_error(ArgumentError)
end
end
end
diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb
index 932d579c3cc..bd9d197afa0 100644
--- a/spec/lib/object_storage/direct_upload_spec.rb
+++ b/spec/lib/object_storage/direct_upload_spec.rb
@@ -162,6 +162,10 @@ RSpec.describe ObjectStorage::DirectUpload do
it 'enables the Workhorse client' do
expect(subject[:UseWorkhorseClient]).to be true
end
+
+ it 'omits the multipart upload URLs' do
+ expect(subject).not_to include(:MultipartUpload)
+ end
end
context 'when only server side encryption is used' do
@@ -292,6 +296,7 @@ RSpec.describe ObjectStorage::DirectUpload do
context 'when IAM profile is true' do
let(:use_iam_profile) { true }
+ let(:iam_credentials_v2_url) { "http://169.254.169.254/latest/api/token" }
let(:iam_credentials_url) { "http://169.254.169.254/latest/meta-data/iam/security-credentials/" }
let(:iam_credentials) do
{
@@ -303,6 +308,9 @@ RSpec.describe ObjectStorage::DirectUpload do
end
before do
+ # If IMDSv2 is disabled, we should still fall back to IMDSv1
+ stub_request(:put, iam_credentials_v2_url)
+ .to_return(status: 404)
stub_request(:get, iam_credentials_url)
.to_return(status: 200, body: "somerole", headers: {})
stub_request(:get, "#{iam_credentials_url}somerole")
@@ -310,6 +318,21 @@ RSpec.describe ObjectStorage::DirectUpload do
end
it_behaves_like 'a valid S3 upload without multipart data'
+
+ context 'when IMSDv2 is available' do
+ let(:iam_token) { 'mytoken' }
+
+ before do
+ stub_request(:put, iam_credentials_v2_url)
+ .to_return(status: 200, body: iam_token)
+ stub_request(:get, iam_credentials_url).with(headers: { "X-aws-ec2-metadata-token" => iam_token })
+ .to_return(status: 200, body: "somerole", headers: {})
+ stub_request(:get, "#{iam_credentials_url}somerole").with(headers: { "X-aws-ec2-metadata-token" => iam_token })
+ .to_return(status: 200, body: iam_credentials.to_json, headers: {})
+ end
+
+ it_behaves_like 'a valid S3 upload without multipart data'
+ end
end
end
@@ -321,6 +344,30 @@ RSpec.describe ObjectStorage::DirectUpload do
stub_object_storage_multipart_init(storage_url, "myUpload")
end
+ context 'when maximum upload size is 0' do
+ let(:maximum_size) { 0 }
+
+ it 'returns maximum number of parts' do
+ expect(subject[:MultipartUpload][:PartURLs].length).to eq(100)
+ end
+
+ it 'part size is minimum, 5MB' do
+ expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte)
+ end
+ end
+
+ context 'when maximum upload size is < 5 MB' do
+ let(:maximum_size) { 1024 }
+
+ it 'returns only 1 part' do
+ expect(subject[:MultipartUpload][:PartURLs].length).to eq(1)
+ end
+
+ it 'part size is minimum, 5MB' do
+ expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte)
+ end
+ end
+
context 'when maximum upload size is 10MB' do
let(:maximum_size) { 10.megabyte }
diff --git a/spec/lib/product_analytics/tracker_spec.rb b/spec/lib/product_analytics/tracker_spec.rb
index 0d0660235f1..52470c9c039 100644
--- a/spec/lib/product_analytics/tracker_spec.rb
+++ b/spec/lib/product_analytics/tracker_spec.rb
@@ -5,53 +5,4 @@ require 'spec_helper'
RSpec.describe ProductAnalytics::Tracker do
it { expect(described_class::URL).to eq('http://localhost/-/sp.js') }
it { expect(described_class::COLLECTOR_URL).to eq('localhost/-/collector') }
-
- describe '.event' do
- after do
- described_class.clear_memoization(:snowplow)
- end
-
- context 'when usage ping is enabled' do
- let(:tracker) { double }
- let(:project_id) { 1 }
-
- before do
- stub_application_setting(usage_ping_enabled: true, self_monitoring_project_id: project_id)
- end
-
- it 'sends an event to Product Analytics snowplow collector' do
- expect(SnowplowTracker::AsyncEmitter)
- .to receive(:new)
- .with(described_class::COLLECTOR_URL, { protocol: 'http' })
- .and_return('_emitter_')
-
- expect(SnowplowTracker::Tracker)
- .to receive(:new)
- .with('_emitter_', an_instance_of(SnowplowTracker::Subject), 'gl', project_id.to_s)
- .and_return(tracker)
-
- freeze_time do
- expect(tracker)
- .to receive(:track_struct_event)
- .with('category', 'action', '_label_', '_property_', '_value_', nil, (Time.current.to_f * 1000).to_i)
-
- described_class.event('category', 'action', label: '_label_', property: '_property_',
- value: '_value_', context: nil)
- end
- end
- end
-
- context 'when usage ping is disabled' do
- before do
- stub_application_setting(usage_ping_enabled: false)
- end
-
- it 'does not send an event' do
- expect(SnowplowTracker::Tracker).not_to receive(:new)
-
- described_class.event('category', 'action', label: '_label_', property: '_property_',
- value: '_value_', context: nil)
- end
- end
- end
end
diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb
index 0239c974755..2232d47234f 100644
--- a/spec/lib/quality/test_level_spec.rb
+++ b/spec/lib/quality/test_level_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a pattern' do
expect(subject.pattern(:unit))
- .to eq("spec/{bin,channels,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,serializers,services,sidekiq,support_specs,tasks,uploaders,validators,views,workers,elastic_integration,tooling}{,/**/}*_spec.rb")
+ .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,serializers,services,sidekiq,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb")
end
end
@@ -103,7 +103,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a regexp' do
expect(subject.regexp(:unit))
- .to eq(%r{spec/(bin|channels|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|serializers|services|sidekiq|support_specs|tasks|uploaders|validators|views|workers|elastic_integration|tooling)})
+ .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|serializers|services|sidekiq|support_specs|tasks|uploaders|validators|views|workers|tooling)})
end
end
@@ -174,6 +174,10 @@ RSpec.describe Quality::TestLevel do
expect(subject.level_for('spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb')).to eq(:migration)
end
+ it 'returns the correct level for an EE file without passing a prefix' do
+ expect(subject.level_for('ee/spec/migrations/geo/migrate_ci_job_artifacts_to_separate_registry_spec.rb')).to eq(:migration)
+ end
+
it 'returns the correct level for a geo migration test' do
expect(described_class.new('ee/').level_for('ee/spec/migrations/geo/migrate_ci_job_artifacts_to_separate_registry_spec.rb')).to eq(:migration)
end
diff --git a/spec/mailers/emails/projects_spec.rb b/spec/mailers/emails/projects_spec.rb
index 6c23625d4a3..a1f19a972f1 100644
--- a/spec/mailers/emails/projects_spec.rb
+++ b/spec/mailers/emails/projects_spec.rb
@@ -19,11 +19,9 @@ RSpec.describe Emails::Projects do
create(:project_incident_management_setting, project: project, create_issue: true)
end
- let(:incident_issues_url) do
- project_issues_url(project, label_name: 'incident')
- end
+ let(:incidents_url) { project_incidents_url(project) }
- it { is_expected.to have_body_text(incident_issues_url) }
+ it { is_expected.to have_body_text(incidents_url) }
end
end
diff --git a/spec/mailers/emails/releases_spec.rb b/spec/mailers/emails/releases_spec.rb
index 60e522c7cfa..6ee87724c83 100644
--- a/spec/mailers/emails/releases_spec.rb
+++ b/spec/mailers/emails/releases_spec.rb
@@ -49,5 +49,14 @@ RSpec.describe Emails::Releases do
is_expected.to have_body_text('Release notes:')
is_expected.to have_body_text(release.description)
end
+
+ context 'release notes with attachment' do
+ let(:upload_path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
+ let(:release) { create(:release, project: project, description: "Attachment: [Test file](#{upload_path})") }
+
+ it 'renders absolute links' do
+ is_expected.to have_body_text(%Q(<a href="#{project.web_url}#{upload_path}" data-link="true" class="gfm">Test file</a>))
+ end
+ end
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 3cc5f202b1f..3ebc2fc1e36 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -1284,7 +1284,7 @@ RSpec.describe Notify do
context 'for service desk issues' do
before do
- issue.update!(service_desk_reply_to: 'service.desk@example.com')
+ issue.update!(external_author: 'service.desk@example.com')
end
def expect_sender(username)
@@ -1450,38 +1450,6 @@ RSpec.describe Notify do
subject { described_class.member_invited_email('Group', group_member.id, group_member.invite_token) }
- shared_examples "tracks the 'sent' event for the invitation reminders experiment" do
- before do
- stub_experiment(invitation_reminders: true)
- allow(Gitlab::Experimentation).to receive(:enabled_for_attribute?).with(:invitation_reminders, group_member.invite_email).and_return(experimental_group)
- end
-
- it "tracks the 'sent' event", :snowplow do
- subject.deliver_now
-
- expect_snowplow_event(
- category: 'Growth::Acquisition::Experiment::InvitationReminders',
- label: Digest::MD5.hexdigest(group_member.to_global_id.to_s),
- property: experimental_group ? 'experimental_group' : 'control_group',
- action: 'sent'
- )
- end
- end
-
- describe 'tracking for the invitation reminders experiment' do
- context 'when invite email is in the experimental group' do
- let(:experimental_group) { true }
-
- it_behaves_like "tracks the 'sent' event for the invitation reminders experiment"
- end
-
- context 'when invite email is in the control group' do
- let(:experimental_group) { false }
-
- it_behaves_like "tracks the 'sent' event for the invitation reminders experiment"
- end
- end
-
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
diff --git a/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb b/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb
index 11398685549..47f85df01ac 100644
--- a/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb
+++ b/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb
@@ -15,35 +15,35 @@ RSpec.describe MigrateIssueTrackersData do
end
let!(:jira_service) do
- services.create(type: 'JiraService', properties: properties, category: 'issue_tracker')
+ services.create!(type: 'JiraService', properties: properties, category: 'issue_tracker')
end
let!(:jira_service_nil) do
- services.create(type: 'JiraService', properties: nil, category: 'issue_tracker')
+ services.create!(type: 'JiraService', properties: nil, category: 'issue_tracker')
end
let!(:bugzilla_service) do
- services.create(type: 'BugzillaService', properties: properties, category: 'issue_tracker')
+ services.create!(type: 'BugzillaService', properties: properties, category: 'issue_tracker')
end
let!(:youtrack_service) do
- services.create(type: 'YoutrackService', properties: properties, category: 'issue_tracker')
+ services.create!(type: 'YoutrackService', properties: properties, category: 'issue_tracker')
end
let!(:youtrack_service_empty) do
- services.create(type: 'YoutrackService', properties: '', category: 'issue_tracker')
+ services.create!(type: 'YoutrackService', properties: '', category: 'issue_tracker')
end
let!(:gitlab_service) do
- services.create(type: 'GitlabIssueTrackerService', properties: properties, category: 'issue_tracker')
+ services.create!(type: 'GitlabIssueTrackerService', properties: properties, category: 'issue_tracker')
end
let!(:gitlab_service_empty) do
- services.create(type: 'GitlabIssueTrackerService', properties: {}, category: 'issue_tracker')
+ services.create!(type: 'GitlabIssueTrackerService', properties: {}, category: 'issue_tracker')
end
let!(:other_service) do
- services.create(type: 'OtherService', properties: properties, category: 'other_category')
+ services.create!(type: 'OtherService', properties: properties, category: 'other_category')
end
before do
diff --git a/spec/migrations/20200122123016_backfill_project_settings_spec.rb b/spec/migrations/20200122123016_backfill_project_settings_spec.rb
index 60c6206a83b..9ae492a36aa 100644
--- a/spec/migrations/20200122123016_backfill_project_settings_spec.rb
+++ b/spec/migrations/20200122123016_backfill_project_settings_spec.rb
@@ -5,16 +5,16 @@ require Rails.root.join('db', 'post_migrate', '20200122123016_backfill_project_s
RSpec.describe BackfillProjectSettings, :sidekiq, schema: 20200114113341 do
let(:projects) { table(:projects) }
- let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
- let(:project) { projects.create(namespace_id: namespace.id) }
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+ let(:project) { projects.create!(namespace_id: namespace.id) }
describe '#up' do
before do
stub_const("#{described_class}::BATCH_SIZE", 2)
- projects.create(id: 1, namespace_id: namespace.id)
- projects.create(id: 2, namespace_id: namespace.id)
- projects.create(id: 3, namespace_id: namespace.id)
+ projects.create!(id: 1, namespace_id: namespace.id)
+ projects.create!(id: 2, namespace_id: namespace.id)
+ projects.create!(id: 3, namespace_id: namespace.id)
end
it 'schedules BackfillProjectSettings background jobs' do
diff --git a/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb b/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb
index e69a30752db..a6ef0b29461 100644
--- a/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb
+++ b/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe RemoveInvalidJiraData do
let(:jira_tracker_data) { table(:jira_tracker_data) }
let(:services) { table(:services) }
- let(:service) { services.create(id: 1) }
+ let(:service) { services.create!(id: 1) }
let(:data) do
{
service_id: service.id,
@@ -22,49 +22,49 @@ RSpec.describe RemoveInvalidJiraData do
}
end
- let!(:valid_data) { jira_tracker_data.create(data) }
- let!(:empty_data) { jira_tracker_data.create(service_id: service.id) }
+ let!(:valid_data) { jira_tracker_data.create!(data) }
+ let!(:empty_data) { jira_tracker_data.create!(service_id: service.id) }
let!(:invalid_api_url) do
data[:encrypted_api_url_iv] = nil
- jira_tracker_data.create(data)
+ jira_tracker_data.create!(data)
end
let!(:missing_api_url) do
data[:encrypted_api_url] = ''
data[:encrypted_api_url_iv] = nil
- jira_tracker_data.create(data)
+ jira_tracker_data.create!(data)
end
let!(:invalid_url) do
data[:encrypted_url_iv] = nil
- jira_tracker_data.create(data)
+ jira_tracker_data.create!(data)
end
let!(:missing_url) do
data[:encrypted_url] = ''
- jira_tracker_data.create(data)
+ jira_tracker_data.create!(data)
end
let!(:invalid_username) do
data[:encrypted_username_iv] = nil
- jira_tracker_data.create(data)
+ jira_tracker_data.create!(data)
end
let!(:missing_username) do
data[:encrypted_username] = nil
data[:encrypted_username_iv] = nil
- jira_tracker_data.create(data)
+ jira_tracker_data.create!(data)
end
let!(:invalid_password) do
data[:encrypted_password_iv] = nil
- jira_tracker_data.create(data)
+ jira_tracker_data.create!(data)
end
let!(:missing_password) do
data[:encrypted_password] = nil
data[:encrypted_username_iv] = nil
- jira_tracker_data.create(data)
+ jira_tracker_data.create!(data)
end
it 'removes the invalid data' do
diff --git a/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb b/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb
index e7917cf5d72..9ddf188b15e 100644
--- a/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb
+++ b/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe RemoveInvalidIssueTrackerData do
let(:issue_tracker_data) { table(:issue_tracker_data) }
let(:services) { table(:services) }
- let(:service) { services.create(id: 1) }
+ let(:service) { services.create!(id: 1) }
let(:data) do
{
service_id: service.id,
@@ -20,38 +20,38 @@ RSpec.describe RemoveInvalidIssueTrackerData do
}
end
- let!(:valid_data) { issue_tracker_data.create(data) }
- let!(:empty_data) { issue_tracker_data.create(service_id: service.id) }
+ let!(:valid_data) { issue_tracker_data.create!(data) }
+ let!(:empty_data) { issue_tracker_data.create!(service_id: service.id) }
let!(:invalid_issues_url) do
data[:encrypted_issues_url_iv] = nil
- issue_tracker_data.create(data)
+ issue_tracker_data.create!(data)
end
let!(:missing_issues_url) do
data[:encrypted_issues_url] = ''
data[:encrypted_issues_url_iv] = nil
- issue_tracker_data.create(data)
+ issue_tracker_data.create!(data)
end
let!(:invalid_new_isue_url) do
data[:encrypted_new_issue_url_iv] = nil
- issue_tracker_data.create(data)
+ issue_tracker_data.create!(data)
end
let!(:missing_new_issue_url) do
data[:encrypted_new_issue_url] = ''
- issue_tracker_data.create(data)
+ issue_tracker_data.create!(data)
end
let!(:invalid_project_url) do
data[:encrypted_project_url_iv] = nil
- issue_tracker_data.create(data)
+ issue_tracker_data.create!(data)
end
let!(:missing_project_url) do
data[:encrypted_project_url] = nil
data[:encrypted_project_url_iv] = nil
- issue_tracker_data.create(data)
+ issue_tracker_data.create!(data)
end
it 'removes the invalid data' do
diff --git a/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb b/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb
index e140be476ab..9947718d11b 100644
--- a/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb
+++ b/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb
@@ -15,35 +15,35 @@ RSpec.describe RescheduleMigrateIssueTrackersData do
end
let!(:jira_service) do
- services.create(id: 10, type: 'JiraService', properties: properties, category: 'issue_tracker')
+ services.create!(id: 10, type: 'JiraService', properties: properties, category: 'issue_tracker')
end
let!(:jira_service_nil) do
- services.create(id: 11, type: 'JiraService', properties: nil, category: 'issue_tracker')
+ services.create!(id: 11, type: 'JiraService', properties: nil, category: 'issue_tracker')
end
let!(:bugzilla_service) do
- services.create(id: 12, type: 'BugzillaService', properties: properties, category: 'issue_tracker')
+ services.create!(id: 12, type: 'BugzillaService', properties: properties, category: 'issue_tracker')
end
let!(:youtrack_service) do
- services.create(id: 13, type: 'YoutrackService', properties: properties, category: 'issue_tracker')
+ services.create!(id: 13, type: 'YoutrackService', properties: properties, category: 'issue_tracker')
end
let!(:youtrack_service_empty) do
- services.create(id: 14, type: 'YoutrackService', properties: '', category: 'issue_tracker')
+ services.create!(id: 14, type: 'YoutrackService', properties: '', category: 'issue_tracker')
end
let!(:gitlab_service) do
- services.create(id: 15, type: 'GitlabIssueTrackerService', properties: properties, category: 'issue_tracker')
+ services.create!(id: 15, type: 'GitlabIssueTrackerService', properties: properties, category: 'issue_tracker')
end
let!(:gitlab_service_empty) do
- services.create(id: 16, type: 'GitlabIssueTrackerService', properties: {}, category: 'issue_tracker')
+ services.create!(id: 16, type: 'GitlabIssueTrackerService', properties: {}, category: 'issue_tracker')
end
let!(:other_service) do
- services.create(id: 17, type: 'OtherService', properties: properties, category: 'other_category')
+ services.create!(id: 17, type: 'OtherService', properties: properties, category: 'other_category')
end
before do
@@ -69,7 +69,7 @@ RSpec.describe RescheduleMigrateIssueTrackersData do
let(:jira_tracker_data) { table(:jira_tracker_data) }
let!(:valid_issue_tracker_data) do
- issue_tracker_data.create(
+ issue_tracker_data.create!(
service_id: bugzilla_service.id,
encrypted_issues_url: 'http://url.com',
encrypted_issues_url_iv: 'somevalue'
@@ -77,7 +77,7 @@ RSpec.describe RescheduleMigrateIssueTrackersData do
end
let!(:invalid_issue_tracker_data) do
- issue_tracker_data.create(
+ issue_tracker_data.create!(
service_id: bugzilla_service.id,
encrypted_issues_url: 'http:url.com',
encrypted_issues_url_iv: nil
@@ -85,7 +85,7 @@ RSpec.describe RescheduleMigrateIssueTrackersData do
end
let!(:valid_jira_tracker_data) do
- jira_tracker_data.create(
+ jira_tracker_data.create!(
service_id: bugzilla_service.id,
encrypted_url: 'http://url.com',
encrypted_url_iv: 'somevalue'
@@ -93,7 +93,7 @@ RSpec.describe RescheduleMigrateIssueTrackersData do
end
let!(:invalid_jira_tracker_data) do
- jira_tracker_data.create(
+ jira_tracker_data.create!(
service_id: bugzilla_service.id,
encrypted_url: 'http://url.com',
encrypted_url_iv: nil
diff --git a/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb b/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb
index d9ce62fe475..f16e7b483e9 100644
--- a/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb
+++ b/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb
@@ -9,9 +9,9 @@ RSpec.describe RemoveOrphanedChatNames, schema: 20200313202430 do
let(:services) { table(:services) }
let(:chat_names) { table(:chat_names) }
- let(:namespace) { namespaces.create(name: 'foo', path: 'foo') }
- let(:project) { projects.create(namespace_id: namespace.id) }
- let(:service) { services.create(project_id: project.id, type: 'chat') }
+ let(:namespace) { namespaces.create!(name: 'foo', path: 'foo') }
+ let(:project) { projects.create!(namespace_id: namespace.id) }
+ let(:service) { services.create!(project_id: project.id, type: 'chat') }
let(:chat_name) { chat_names.create!(service_id: service.id, team_id: 'TEAM', user_id: 12345, chat_id: 12345) }
let(:orphaned_chat_name) { chat_names.create!(team_id: 'TEAM', service_id: 0, user_id: 12345, chat_id: 12345) }
diff --git a/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb b/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb
index c6b221b09a5..9950cc23889 100644
--- a/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb
+++ b/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb
@@ -8,10 +8,10 @@ RSpec.describe BackfillDeploymentClustersFromDeployments, :migration, :sidekiq,
it 'schedules BackfillDeploymentClustersFromDeployments background jobs' do
stub_const("#{described_class}::BATCH_SIZE", 2)
- namespace = table(:namespaces).create(name: 'the-namespace', path: 'the-path')
- project = table(:projects).create(name: 'the-project', namespace_id: namespace.id)
- environment = table(:environments).create(name: 'the-environment', project_id: project.id, slug: 'slug')
- cluster = table(:clusters).create(name: 'the-cluster')
+ namespace = table(:namespaces).create!(name: 'the-namespace', path: 'the-path')
+ project = table(:projects).create!(name: 'the-project', namespace_id: namespace.id)
+ environment = table(:environments).create!(name: 'the-environment', project_id: project.id, slug: 'slug')
+ cluster = table(:clusters).create!(name: 'the-cluster')
deployment_data = { cluster_id: cluster.id, project_id: project.id, environment_id: environment.id, ref: 'abc', tag: false, sha: 'sha', status: 1 }
@@ -44,7 +44,7 @@ RSpec.describe BackfillDeploymentClustersFromDeployments, :migration, :sidekiq,
def create_deployment(**data)
@iid ||= 0
@iid += 1
- table(:deployments).create(iid: @iid, **data)
+ table(:deployments).create!(iid: @iid, **data)
end
end
end
diff --git a/spec/migrations/20200526115436_dedup_mr_metrics_spec.rb b/spec/migrations/20200526115436_dedup_mr_metrics_spec.rb
index f2698a0f352..9d3851ed5b0 100644
--- a/spec/migrations/20200526115436_dedup_mr_metrics_spec.rb
+++ b/spec/migrations/20200526115436_dedup_mr_metrics_spec.rb
@@ -10,19 +10,19 @@ RSpec.describe DedupMrMetrics, :migration, schema: 20200526013844 do
let(:metrics) { table(:merge_request_metrics) }
let(:merge_request_params) { { source_branch: 'x', target_branch: 'y', target_project_id: project.id } }
- let!(:namespace) { namespaces.create(name: 'foo', path: 'foo') }
+ let!(:namespace) { namespaces.create!(name: 'foo', path: 'foo') }
let!(:project) { projects.create!(namespace_id: namespace.id) }
let!(:merge_request_1) { merge_requests.create!(merge_request_params) }
let!(:merge_request_2) { merge_requests.create!(merge_request_params) }
let!(:merge_request_3) { merge_requests.create!(merge_request_params) }
- let!(:duplicated_metrics_1) { metrics.create(merge_request_id: merge_request_1.id, latest_build_started_at: 1.day.ago, first_deployed_to_production_at: 5.days.ago, updated_at: 2.months.ago) }
- let!(:duplicated_metrics_2) { metrics.create(merge_request_id: merge_request_1.id, latest_build_started_at: Time.now, merged_at: Time.now, updated_at: 1.month.ago) }
+ let!(:duplicated_metrics_1) { metrics.create!(merge_request_id: merge_request_1.id, latest_build_started_at: 1.day.ago, first_deployed_to_production_at: 5.days.ago, updated_at: 2.months.ago) }
+ let!(:duplicated_metrics_2) { metrics.create!(merge_request_id: merge_request_1.id, latest_build_started_at: Time.now, merged_at: Time.now, updated_at: 1.month.ago) }
- let!(:duplicated_metrics_3) { metrics.create(merge_request_id: merge_request_3.id, diff_size: 30, commits_count: 20, updated_at: 2.months.ago) }
- let!(:duplicated_metrics_4) { metrics.create(merge_request_id: merge_request_3.id, added_lines: 5, commits_count: nil, updated_at: 1.month.ago) }
+ let!(:duplicated_metrics_3) { metrics.create!(merge_request_id: merge_request_3.id, diff_size: 30, commits_count: 20, updated_at: 2.months.ago) }
+ let!(:duplicated_metrics_4) { metrics.create!(merge_request_id: merge_request_3.id, added_lines: 5, commits_count: nil, updated_at: 1.month.ago) }
- let!(:non_duplicated_metrics) { metrics.create(merge_request_id: merge_request_2.id, latest_build_started_at: 2.days.ago) }
+ let!(:non_duplicated_metrics) { metrics.create!(merge_request_id: merge_request_2.id, latest_build_started_at: 2.days.ago) }
it 'deduplicates merge_request_metrics table' do
expect { migrate! }.to change { metrics.count }.from(5).to(3)
diff --git a/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb b/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb
index f1fe27e72a1..b33320c922a 100644
--- a/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb
+++ b/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb
@@ -6,7 +6,7 @@ require Rails.root.join('db', 'migrate', '20200122161638_add_deploy_token_type_t
RSpec.describe AddDeployTokenTypeToDeployTokens do
let(:deploy_tokens) { table(:deploy_tokens) }
let(:deploy_token) do
- deploy_tokens.create(name: 'token_test',
+ deploy_tokens.create!(name: 'token_test',
username: 'gitlab+deploy-token-1',
token_encrypted: 'dr8rPXwM+Mbs2p3Bg1+gpnXqrnH/wu6vaHdcc7A3isPR67WB',
read_repository: true,
diff --git a/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb b/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb
index dab42c0ffc3..a62fc43df02 100644
--- a/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb
+++ b/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe AddIncidentSettingsToAllExistingProjects, :migration do
RSpec.shared_context 'with incident settings' do
let(:existing_create_issue) { false }
before do
- project_incident_management_settings.create(
+ project_incident_management_settings.create!(
project_id: project.id,
create_issue: existing_create_issue
)
diff --git a/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb b/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb
index 6c04be5fc60..391bc59cdc0 100644
--- a/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb
+++ b/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb
@@ -12,15 +12,15 @@ RSpec.describe AddUniqueConstraintToApprovalsUserIdAndMergeRequestId do
describe '#up' do
before do
- namespaces.create(id: 1, name: 'ns', path: 'ns')
- projects.create(id: 1, namespace_id: 1)
- merge_requests.create(id: 1, target_branch: 'master', source_branch: 'feature-1', target_project_id: 1)
- merge_requests.create(id: 2, target_branch: 'master', source_branch: 'feature-2', target_project_id: 1)
+ namespaces.create!(id: 1, name: 'ns', path: 'ns')
+ projects.create!(id: 1, namespace_id: 1)
+ merge_requests.create!(id: 1, target_branch: 'master', source_branch: 'feature-1', target_project_id: 1)
+ merge_requests.create!(id: 2, target_branch: 'master', source_branch: 'feature-2', target_project_id: 1)
end
it 'deletes duplicate records and keeps the first one' do
- first_approval = approvals.create(id: 1, merge_request_id: 1, user_id: 1)
- approvals.create(id: 2, merge_request_id: 1, user_id: 1)
+ first_approval = approvals.create!(id: 1, merge_request_id: 1, user_id: 1)
+ approvals.create!(id: 2, merge_request_id: 1, user_id: 1)
migration.up
diff --git a/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
index 9d0ad5863c6..6c782af9a31 100644
--- a/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
+++ b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable
it 'fills released_at with the value of created_at' do
created_at_a = Time.zone.parse('2019-02-10T08:00:00Z')
created_at_b = Time.zone.parse('2019-03-10T18:00:00Z')
- namespace = namespaces.create(name: 'foo', path: 'foo')
+ namespace = namespaces.create!(name: 'foo', path: 'foo')
project = projects.create!(namespace_id: namespace.id)
release_a = releases.create!(project_id: project.id, created_at: created_at_a)
release_b = releases.create!(project_id: project.id, created_at: created_at_b)
diff --git a/spec/migrations/backfill_imported_snippet_repositories_spec.rb b/spec/migrations/backfill_imported_snippet_repositories_spec.rb
index 998a691bf77..356551f176e 100644
--- a/spec/migrations/backfill_imported_snippet_repositories_spec.rb
+++ b/spec/migrations/backfill_imported_snippet_repositories_spec.rb
@@ -6,7 +6,7 @@ require Rails.root.join('db', 'post_migrate', '20200608072931_backfill_imported_
RSpec.describe BackfillImportedSnippetRepositories do
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
- let(:user) { users.create(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test', state: 'active') }
+ let(:user) { users.create!(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test', state: 'active') }
def create_snippet(id)
params = {
diff --git a/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb b/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb
index 4c3517bc574..22c93d37816 100644
--- a/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb
+++ b/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe BackfillReleasesTableUpdatedAtAndAddNotNullConstraintsToTimestamp
it 'fills null updated_at rows with the value of created_at' do
created_at_a = Time.zone.parse('2014-03-11T04:30:00Z')
created_at_b = Time.zone.parse('2019-09-10T12:00:00Z')
- namespace = namespaces.create(name: 'foo', path: 'foo')
+ namespace = namespaces.create!(name: 'foo', path: 'foo')
project = projects.create!(namespace_id: namespace.id)
release_a = releases.create!(project_id: project.id,
released_at: Time.zone.parse('2014-12-10T06:00:00Z'),
@@ -36,7 +36,7 @@ RSpec.describe BackfillReleasesTableUpdatedAtAndAddNotNullConstraintsToTimestamp
created_at_a = Time.zone.parse('2014-03-11T04:30:00Z')
updated_at_a = Time.zone.parse('2015-01-16T10:00:00Z')
created_at_b = Time.zone.parse('2019-09-10T12:00:00Z')
- namespace = namespaces.create(name: 'foo', path: 'foo')
+ namespace = namespaces.create!(name: 'foo', path: 'foo')
project = projects.create!(namespace_id: namespace.id)
release_a = releases.create!(project_id: project.id,
released_at: Time.zone.parse('2014-12-10T06:00:00Z'),
diff --git a/spec/migrations/backfill_snippet_repositories_spec.rb b/spec/migrations/backfill_snippet_repositories_spec.rb
index 6d417669ad6..b176b8d49ec 100644
--- a/spec/migrations/backfill_snippet_repositories_spec.rb
+++ b/spec/migrations/backfill_snippet_repositories_spec.rb
@@ -6,7 +6,7 @@ require Rails.root.join('db', 'post_migrate', '20200420094444_backfill_snippet_r
RSpec.describe BackfillSnippetRepositories do
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
- let(:user) { users.create(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test', state: 'active') }
+ let(:user) { users.create!(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test', state: 'active') }
def create_snippet(id)
params = {
diff --git a/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb b/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb
index ff5aa81b5b5..7cd3646d107 100644
--- a/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb
+++ b/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe EncryptPlaintextAttributesOnApplicationSettings do
describe '#up' do
it 'encrypts token and saves it' do
- application_setting = application_settings.create
+ application_setting = application_settings.create!
application_setting.update_columns(
plaintext_attributes.each_with_object({}) do |plaintext_attribute, attributes|
attributes[plaintext_attribute] = plaintext
@@ -39,7 +39,7 @@ RSpec.describe EncryptPlaintextAttributesOnApplicationSettings do
describe '#down' do
it 'decrypts encrypted token and saves it' do
- application_setting = application_settings.create(
+ application_setting = application_settings.create!(
plaintext_attributes.each_with_object({}) do |plaintext_attribute, attributes|
attributes[plaintext_attribute] = plaintext
end
diff --git a/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb b/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
index d4189326366..2bbc67c4db3 100644
--- a/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
+++ b/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
@@ -6,8 +6,8 @@ require Rails.root.join('db', 'post_migrate', '20190620112608_enqueue_reset_merg
RSpec.describe EnqueueResetMergeStatusSecondRun do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
- let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
- let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') }
let(:merge_requests) { table(:merge_requests) }
def create_merge_request(id, extra_params = {})
diff --git a/spec/migrations/enqueue_reset_merge_status_spec.rb b/spec/migrations/enqueue_reset_merge_status_spec.rb
index c581293ddcd..0843bbacd5f 100644
--- a/spec/migrations/enqueue_reset_merge_status_spec.rb
+++ b/spec/migrations/enqueue_reset_merge_status_spec.rb
@@ -6,8 +6,8 @@ require Rails.root.join('db', 'post_migrate', '20190528180441_enqueue_reset_merg
RSpec.describe EnqueueResetMergeStatus do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
- let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
- let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') }
let(:merge_requests) { table(:merge_requests) }
def create_merge_request(id, extra_params = {})
diff --git a/spec/migrations/ensure_namespace_settings_creation_spec.rb b/spec/migrations/ensure_namespace_settings_creation_spec.rb
index 8574063f7fe..cececc1e569 100644
--- a/spec/migrations/ensure_namespace_settings_creation_spec.rb
+++ b/spec/migrations/ensure_namespace_settings_creation_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe EnsureNamespaceSettingsCreation do
end
end
- it 'schedules migrations in batches ' do
+ it 'schedules migrations in batches' do
stub_const("#{described_class.name}::BATCH_SIZE", 2)
namespace_3 = namespaces.create!(name: 'gitlab', path: 'gitlab-org3')
diff --git a/spec/migrations/ensure_u2f_registrations_migrated_spec.rb b/spec/migrations/ensure_u2f_registrations_migrated_spec.rb
new file mode 100644
index 00000000000..77eab3b829a
--- /dev/null
+++ b/spec/migrations/ensure_u2f_registrations_migrated_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20201026185514_ensure_u2f_registrations_migrated.rb')
+
+RSpec.describe EnsureU2fRegistrationsMigrated, schema: 20201022144501 do
+ let(:u2f_registrations) { table(:u2f_registrations) }
+ let(:webauthn_registrations) { table(:webauthn_registrations) }
+ let(:users) { table(:users) }
+
+ let(:user) { users.create!(email: 'email@email.com', name: 'foo', username: 'foo', projects_limit: 0) }
+
+ before do
+ create_u2f_registration(1, 'reg1')
+ create_u2f_registration(2, 'reg2')
+ webauthn_registrations.create!({ name: 'reg1', u2f_registration_id: 1, credential_xid: '', public_key: '', user_id: user.id })
+ end
+
+ it 'correctly migrates u2f registrations previously not migrated' do
+ expect { migrate! }.to change { webauthn_registrations.count }.from(1).to(2)
+ end
+
+ it 'migrates all valid u2f registrations depite errors' do
+ create_u2f_registration(3, 'reg3', 'invalid!')
+ create_u2f_registration(4, 'reg4')
+
+ expect { migrate! }.to change { webauthn_registrations.count }.from(1).to(3)
+ end
+
+ def create_u2f_registration(id, name, public_key = nil)
+ device = U2F::FakeU2F.new(FFaker::BaconIpsum.characters(5), { key_handle: SecureRandom.random_bytes(255) })
+ public_key ||= Base64.strict_encode64(device.origin_public_key_raw)
+ u2f_registrations.create!({ id: id,
+ certificate: Base64.strict_encode64(device.cert_raw),
+ key_handle: U2F.urlsafe_encode64(device.key_handle_raw),
+ public_key: public_key,
+ counter: 5,
+ name: name,
+ user_id: user.id })
+ end
+end
diff --git a/spec/migrations/fill_file_store_lfs_objects_spec.rb b/spec/migrations/fill_file_store_lfs_objects_spec.rb
index 2a610e5311b..23063cc9cd0 100644
--- a/spec/migrations/fill_file_store_lfs_objects_spec.rb
+++ b/spec/migrations/fill_file_store_lfs_objects_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe FillFileStoreLfsObjects do
context 'when file_store is nil' do
it 'updates file_store to local' do
- lfs_objects.create(oid: oid, size: 1062, file_store: nil)
+ lfs_objects.create!(oid: oid, size: 1062, file_store: nil)
lfs_object = lfs_objects.find_by(oid: oid)
expect { migrate! }.to change { lfs_object.reload.file_store }.from(nil).to(1)
@@ -18,7 +18,7 @@ RSpec.describe FillFileStoreLfsObjects do
context 'when file_store is set to local' do
it 'does not update file_store' do
- lfs_objects.create(oid: oid, size: 1062, file_store: 1)
+ lfs_objects.create!(oid: oid, size: 1062, file_store: 1)
lfs_object = lfs_objects.find_by(oid: oid)
expect { migrate! }.not_to change { lfs_object.reload.file_store }
@@ -27,7 +27,7 @@ RSpec.describe FillFileStoreLfsObjects do
context 'when file_store is set to object storage' do
it 'does not update file_store' do
- lfs_objects.create(oid: oid, size: 1062, file_store: 2)
+ lfs_objects.create!(oid: oid, size: 1062, file_store: 2)
lfs_object = lfs_objects.find_by(oid: oid)
expect { migrate! }.not_to change { lfs_object.reload.file_store }
diff --git a/spec/migrations/fill_store_uploads_spec.rb b/spec/migrations/fill_store_uploads_spec.rb
index 60eaf982c57..bcb5d45e1c0 100644
--- a/spec/migrations/fill_store_uploads_spec.rb
+++ b/spec/migrations/fill_store_uploads_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe FillStoreUploads do
context 'when store is nil' do
it 'updates store to local' do
- uploads.create(size: 100.kilobytes,
+ uploads.create!(size: 100.kilobytes,
uploader: 'AvatarUploader',
path: path,
store: nil)
@@ -22,7 +22,7 @@ RSpec.describe FillStoreUploads do
context 'when store is set to local' do
it 'does not update store' do
- uploads.create(size: 100.kilobytes,
+ uploads.create!(size: 100.kilobytes,
uploader: 'AvatarUploader',
path: path,
store: 1)
@@ -35,7 +35,7 @@ RSpec.describe FillStoreUploads do
context 'when store is set to object storage' do
it 'does not update store' do
- uploads.create(size: 100.kilobytes,
+ uploads.create!(size: 100.kilobytes,
uploader: 'AvatarUploader',
path: path,
store: 2)
diff --git a/spec/migrations/fix_null_type_labels_spec.rb b/spec/migrations/fix_null_type_labels_spec.rb
index fadc2a5d29e..b3fc0e30687 100644
--- a/spec/migrations/fix_null_type_labels_spec.rb
+++ b/spec/migrations/fix_null_type_labels_spec.rb
@@ -10,14 +10,14 @@ RSpec.describe FixNullTypeLabels do
let(:labels) { table(:labels) }
before do
- group = namespaces.create(name: 'labels-test-project', path: 'labels-test-project', type: 'Group')
+ group = namespaces.create!(name: 'labels-test-project', path: 'labels-test-project', type: 'Group')
project = projects.create!(namespace_id: group.id, name: 'labels-test-group', path: 'labels-test-group')
- @template_label = labels.create(title: 'template', template: true)
- @project_label = labels.create(title: 'project label', project_id: project.id, type: 'ProjectLabel')
- @group_label = labels.create(title: 'group_label', group_id: group.id, type: 'GroupLabel')
- @broken_label_1 = labels.create(title: 'broken 1', project_id: project.id)
- @broken_label_2 = labels.create(title: 'broken 2', project_id: project.id)
+ @template_label = labels.create!(title: 'template', template: true)
+ @project_label = labels.create!(title: 'project label', project_id: project.id, type: 'ProjectLabel')
+ @group_label = labels.create!(title: 'group_label', group_id: group.id, type: 'GroupLabel')
+ @broken_label_1 = labels.create!(title: 'broken 1', project_id: project.id)
+ @broken_label_2 = labels.create!(title: 'broken 2', project_id: project.id)
end
describe '#up' do
diff --git a/spec/migrations/fix_pool_repository_source_project_id_spec.rb b/spec/migrations/fix_pool_repository_source_project_id_spec.rb
index 67a75b893ef..3413cef3c8b 100644
--- a/spec/migrations/fix_pool_repository_source_project_id_spec.rb
+++ b/spec/migrations/fix_pool_repository_source_project_id_spec.rb
@@ -13,11 +13,11 @@ RSpec.describe FixPoolRepositorySourceProjectId do
# gitaly is a project with a pool repository that has a source_project_id
gitaly = projects.create!(name: 'gitaly', path: 'gitlab-org/gitaly', namespace_id: 1)
- pool_repository = pool_repositories.create(shard_id: shard.id, source_project_id: gitaly.id)
+ pool_repository = pool_repositories.create!(shard_id: shard.id, source_project_id: gitaly.id)
gitaly.update_column(:pool_repository_id, pool_repository.id)
# gitlab is a project with a pool repository that's missing a source_project_id
- pool_repository_without_source_project = pool_repositories.create(shard_id: shard.id, source_project_id: nil)
+ pool_repository_without_source_project = pool_repositories.create!(shard_id: shard.id, source_project_id: nil)
gitlab = projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1, pool_repository_id: pool_repository_without_source_project.id)
projects.create!(name: 'gitlab-fork-1', path: 'my-org-1/gitlab-ce', namespace_id: 1, pool_repository_id: pool_repository_without_source_project.id)
diff --git a/spec/migrations/fix_projects_without_project_feature_spec.rb b/spec/migrations/fix_projects_without_project_feature_spec.rb
index 1ec67c07ba3..696c9e86384 100644
--- a/spec/migrations/fix_projects_without_project_feature_spec.rb
+++ b/spec/migrations/fix_projects_without_project_feature_spec.rb
@@ -4,13 +4,13 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200127111840_fix_projects_without_project_feature.rb')
RSpec.describe FixProjectsWithoutProjectFeature do
- let(:namespace) { table(:namespaces).create(name: 'gitlab', path: 'gitlab-org') }
+ let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab-org') }
let!(:projects) do
[
- table(:projects).create(namespace_id: namespace.id, name: 'foo 1'),
- table(:projects).create(namespace_id: namespace.id, name: 'foo 2'),
- table(:projects).create(namespace_id: namespace.id, name: 'foo 3')
+ table(:projects).create!(namespace_id: namespace.id, name: 'foo 1'),
+ table(:projects).create!(namespace_id: namespace.id, name: 'foo 2'),
+ table(:projects).create!(namespace_id: namespace.id, name: 'foo 3')
]
end
diff --git a/spec/migrations/fix_projects_without_prometheus_services_spec.rb b/spec/migrations/fix_projects_without_prometheus_services_spec.rb
index a4504ab6773..987350847ca 100644
--- a/spec/migrations/fix_projects_without_prometheus_services_spec.rb
+++ b/spec/migrations/fix_projects_without_prometheus_services_spec.rb
@@ -4,13 +4,13 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200220115023_fix_projects_without_prometheus_service.rb')
RSpec.describe FixProjectsWithoutPrometheusService, :migration do
- let(:namespace) { table(:namespaces).create(name: 'gitlab', path: 'gitlab-org') }
+ let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab-org') }
let!(:projects) do
[
- table(:projects).create(namespace_id: namespace.id, name: 'foo 1'),
- table(:projects).create(namespace_id: namespace.id, name: 'foo 2'),
- table(:projects).create(namespace_id: namespace.id, name: 'foo 3')
+ table(:projects).create!(namespace_id: namespace.id, name: 'foo 1'),
+ table(:projects).create!(namespace_id: namespace.id, name: 'foo 2'),
+ table(:projects).create!(namespace_id: namespace.id, name: 'foo 3')
]
end
diff --git a/spec/migrations/fix_wrong_pages_access_level_spec.rb b/spec/migrations/fix_wrong_pages_access_level_spec.rb
index 80787e0f115..310076f2e0a 100644
--- a/spec/migrations/fix_wrong_pages_access_level_spec.rb
+++ b/spec/migrations/fix_wrong_pages_access_level_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe FixWrongPagesAccessLevel, :sidekiq_might_not_need_inline, schema:
let(:features_table) { table(:project_features) }
let(:subgroup) do
- root_group = namespaces_table.create(path: "group", name: "group")
+ root_group = namespaces_table.create!(path: "group", name: "group")
namespaces_table.create!(path: "subgroup", name: "group", parent_id: root_group.id)
end
diff --git a/spec/migrations/insert_project_hooks_plan_limits_spec.rb b/spec/migrations/insert_project_hooks_plan_limits_spec.rb
index 09fae160a6f..f29d72168bc 100644
--- a/spec/migrations/insert_project_hooks_plan_limits_spec.rb
+++ b/spec/migrations/insert_project_hooks_plan_limits_spec.rb
@@ -9,11 +9,11 @@ RSpec.describe InsertProjectHooksPlanLimits do
let(:plan_limits) { table(:plan_limits) }
before do
- plans.create(id: 34, name: 'free')
- plans.create(id: 2, name: 'bronze')
- plans.create(id: 3, name: 'silver')
- plans.create(id: 4, name: 'gold')
- plan_limits.create(plan_id: 34, ci_active_jobs: 5)
+ plans.create!(id: 34, name: 'free')
+ plans.create!(id: 2, name: 'bronze')
+ plans.create!(id: 3, name: 'silver')
+ plans.create!(id: 4, name: 'gold')
+ plan_limits.create!(plan_id: 34, ci_active_jobs: 5)
end
context 'when on Gitlab.com' do
diff --git a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
index d0abc777326..c7c846c7ef3 100644
--- a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
+++ b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
@@ -80,7 +80,7 @@ RSpec.describe MigrateAutoDevOpsDomainToClusterDomain do
cluster_projects.each do |cluster_project|
specific_domain = "#{cluster_project.id}-#{domain}" if domain
- project_auto_devops_table.create(
+ project_auto_devops_table.create!(
project_id: cluster_project.project_id,
enabled: true,
domain: specific_domain
diff --git a/spec/migrations/move_limits_from_plans_spec.rb b/spec/migrations/move_limits_from_plans_spec.rb
index a7a7d540ed3..c65fc439dd6 100644
--- a/spec/migrations/move_limits_from_plans_spec.rb
+++ b/spec/migrations/move_limits_from_plans_spec.rb
@@ -7,11 +7,11 @@ RSpec.describe MoveLimitsFromPlans do
let(:plans) { table(:plans) }
let(:plan_limits) { table(:plan_limits) }
- let!(:gold_plan) { plans.create(name: 'gold', title: 'Gold', active_pipelines_limit: 20, pipeline_size_limit: 21, active_jobs_limit: 22) }
- let!(:silver_plan) { plans.create(name: 'silver', title: 'Silver', active_pipelines_limit: 30, pipeline_size_limit: 31, active_jobs_limit: 32) }
- let!(:bronze_plan) { plans.create(name: 'bronze', title: 'Bronze', active_pipelines_limit: 40, pipeline_size_limit: 41, active_jobs_limit: 42) }
- let!(:free_plan) { plans.create(name: 'free', title: 'Free', active_pipelines_limit: 50, pipeline_size_limit: 51, active_jobs_limit: 52) }
- let!(:other_plan) { plans.create(name: 'other', title: 'Other', active_pipelines_limit: nil, pipeline_size_limit: nil, active_jobs_limit: 0) }
+ let!(:gold_plan) { plans.create!(name: 'gold', title: 'Gold', active_pipelines_limit: 20, pipeline_size_limit: 21, active_jobs_limit: 22) }
+ let!(:silver_plan) { plans.create!(name: 'silver', title: 'Silver', active_pipelines_limit: 30, pipeline_size_limit: 31, active_jobs_limit: 32) }
+ let!(:bronze_plan) { plans.create!(name: 'bronze', title: 'Bronze', active_pipelines_limit: 40, pipeline_size_limit: 41, active_jobs_limit: 42) }
+ let!(:free_plan) { plans.create!(name: 'free', title: 'Free', active_pipelines_limit: 50, pipeline_size_limit: 51, active_jobs_limit: 52) }
+ let!(:other_plan) { plans.create!(name: 'other', title: 'Other', active_pipelines_limit: nil, pipeline_size_limit: nil, active_jobs_limit: 0) }
describe 'migrate' do
it 'populates plan_limits from all the records in plans' do
diff --git a/spec/migrations/populate_project_statistics_packages_size_spec.rb b/spec/migrations/populate_project_statistics_packages_size_spec.rb
index 9024406c614..6c838e83c56 100644
--- a/spec/migrations/populate_project_statistics_packages_size_spec.rb
+++ b/spec/migrations/populate_project_statistics_packages_size_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe PopulateProjectStatisticsPackagesSize do
let(:artifacts_size) { 4.terabytes }
let(:storage_size) { repo_size + lfs_size + artifacts_size }
- let(:namespace) { namespaces.create(name: 'foo', path: 'foo') }
+ let(:namespace) { namespaces.create!(name: 'foo', path: 'foo') }
let(:package) { packages.create!(project_id: project.id, name: 'a package', package_type: 1) }
let(:project) { projects.create!(namespace_id: namespace.id) }
diff --git a/spec/migrations/populate_remaining_missing_dismissal_information_for_vulnerabilities_spec.rb b/spec/migrations/populate_remaining_missing_dismissal_information_for_vulnerabilities_spec.rb
new file mode 100644
index 00000000000..986436971ac
--- /dev/null
+++ b/spec/migrations/populate_remaining_missing_dismissal_information_for_vulnerabilities_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe PopulateRemainingMissingDismissalInformationForVulnerabilities do
+ let(:users) { table(:users) }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:vulnerabilities) { table(:vulnerabilities) }
+
+ let(:user) { users.create!(name: 'test', email: 'test@example.com', projects_limit: 5) }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') }
+
+ let(:states) { { detected: 1, dismissed: 2, resolved: 3, confirmed: 4 } }
+ let!(:vulnerability_1) { vulnerabilities.create!(title: 'title', state: states[:detected], severity: 0, confidence: 5, report_type: 2, project_id: project.id, author_id: user.id) }
+ let!(:vulnerability_2) { vulnerabilities.create!(title: 'title', state: states[:dismissed], severity: 0, confidence: 5, report_type: 2, project_id: project.id, author_id: user.id) }
+ let!(:vulnerability_3) { vulnerabilities.create!(title: 'title', state: states[:resolved], severity: 0, confidence: 5, report_type: 2, project_id: project.id, author_id: user.id) }
+ let!(:vulnerability_4) { vulnerabilities.create!(title: 'title', state: states[:confirmed], severity: 0, confidence: 5, report_type: 2, project_id: project.id, author_id: user.id) }
+
+ describe '#perform' do
+ it 'calls the background migration class instance with broken vulnerability IDs' do
+ expect_next_instance_of(::Gitlab::BackgroundMigration::PopulateMissingVulnerabilityDismissalInformation) do |migrator|
+ expect(migrator).to receive(:perform).with(vulnerability_2.id)
+ end
+
+ migrate!
+ end
+ end
+end
diff --git a/spec/migrations/remove_orphan_service_hooks_spec.rb b/spec/migrations/remove_orphan_service_hooks_spec.rb
new file mode 100644
index 00000000000..c06a8b97738
--- /dev/null
+++ b/spec/migrations/remove_orphan_service_hooks_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+require Rails.root.join('db', 'migrate', '20201119125730_add_web_hooks_service_foreign_key.rb')
+
+RSpec.describe RemoveOrphanServiceHooks, schema: 20201203123201 do
+ let(:web_hooks) { table(:web_hooks) }
+ let(:services) { table(:services) }
+
+ before do
+ services.create!
+ web_hooks.create!(service_id: services.first.id, type: 'ServiceHook')
+ web_hooks.create!(service_id: nil)
+
+ AddWebHooksServiceForeignKey.new.down
+ web_hooks.create!(service_id: non_existing_record_id, type: 'ServiceHook')
+ AddWebHooksServiceForeignKey.new.up
+ end
+
+ it 'removes service hooks where the referenced service does not exist', :aggregate_failures do
+ expect { RemoveOrphanServiceHooks.new.up }.to change { web_hooks.count }.by(-1)
+ expect(web_hooks.where.not(service_id: services.select(:id)).count).to eq(0)
+ end
+end
diff --git a/spec/migrations/schedule_link_lfs_objects_projects_spec.rb b/spec/migrations/schedule_link_lfs_objects_projects_spec.rb
index 04ca18de91f..2384c5d5ce7 100644
--- a/spec/migrations/schedule_link_lfs_objects_projects_spec.rb
+++ b/spec/migrations/schedule_link_lfs_objects_projects_spec.rb
@@ -11,42 +11,42 @@ RSpec.describe ScheduleLinkLfsObjectsProjects, :migration, :sidekiq do
let(:lfs_objects) { table(:lfs_objects) }
let(:lfs_objects_projects) { table(:lfs_objects_projects) }
- let(:namespace) { namespaces.create(name: 'GitLab', path: 'gitlab') }
+ let(:namespace) { namespaces.create!(name: 'GitLab', path: 'gitlab') }
- let(:fork_network) { fork_networks.create(root_project_id: source_project.id) }
- let(:another_fork_network) { fork_networks.create(root_project_id: another_source_project.id) }
+ let(:fork_network) { fork_networks.create!(root_project_id: source_project.id) }
+ let(:another_fork_network) { fork_networks.create!(root_project_id: another_source_project.id) }
- let(:source_project) { projects.create(namespace_id: namespace.id) }
- let(:another_source_project) { projects.create(namespace_id: namespace.id) }
- let(:project) { projects.create(namespace_id: namespace.id) }
- let(:another_project) { projects.create(namespace_id: namespace.id) }
+ let(:source_project) { projects.create!(namespace_id: namespace.id) }
+ let(:another_source_project) { projects.create!(namespace_id: namespace.id) }
+ let(:project) { projects.create!(namespace_id: namespace.id) }
+ let(:another_project) { projects.create!(namespace_id: namespace.id) }
- let(:lfs_object) { lfs_objects.create(oid: 'abc123', size: 100) }
- let(:another_lfs_object) { lfs_objects.create(oid: 'def456', size: 200) }
+ let(:lfs_object) { lfs_objects.create!(oid: 'abc123', size: 100) }
+ let(:another_lfs_object) { lfs_objects.create!(oid: 'def456', size: 200) }
let!(:source_project_lop_1) do
- lfs_objects_projects.create(
+ lfs_objects_projects.create!(
lfs_object_id: lfs_object.id,
project_id: source_project.id
)
end
let!(:source_project_lop_2) do
- lfs_objects_projects.create(
+ lfs_objects_projects.create!(
lfs_object_id: another_lfs_object.id,
project_id: source_project.id
)
end
let!(:another_source_project_lop_1) do
- lfs_objects_projects.create(
+ lfs_objects_projects.create!(
lfs_object_id: lfs_object.id,
project_id: another_source_project.id
)
end
let!(:another_source_project_lop_2) do
- lfs_objects_projects.create(
+ lfs_objects_projects.create!(
lfs_object_id: another_lfs_object.id,
project_id: another_source_project.id
)
@@ -56,10 +56,10 @@ RSpec.describe ScheduleLinkLfsObjectsProjects, :migration, :sidekiq do
stub_const("#{described_class.name}::BATCH_SIZE", 2)
# Create links between projects
- fork_network_members.create(fork_network_id: fork_network.id, project_id: source_project.id, forked_from_project_id: nil)
- fork_network_members.create(fork_network_id: fork_network.id, project_id: project.id, forked_from_project_id: source_project.id)
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: another_source_project.id, forked_from_project_id: nil)
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: another_project.id, forked_from_project_id: another_fork_network.root_project_id)
+ fork_network_members.create!(fork_network_id: fork_network.id, project_id: source_project.id, forked_from_project_id: nil)
+ fork_network_members.create!(fork_network_id: fork_network.id, project_id: project.id, forked_from_project_id: source_project.id)
+ fork_network_members.create!(fork_network_id: another_fork_network.id, project_id: another_source_project.id, forked_from_project_id: nil)
+ fork_network_members.create!(fork_network_id: another_fork_network.id, project_id: another_project.id, forked_from_project_id: another_fork_network.root_project_id)
end
it 'schedules background migration to link LFS objects' do
diff --git a/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
index 93185b330c7..1f44df82ad0 100644
--- a/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
+++ b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
@@ -6,8 +6,8 @@ require Rails.root.join('db', 'post_migrate', '20190322132835_schedule_populate_
RSpec.describe SchedulePopulateMergeRequestAssigneesTable do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
- let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
- let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') }
let(:merge_requests) { table(:merge_requests) }
def create_merge_request(id)
diff --git a/spec/migrations/schedule_repopulate_historical_vulnerability_statistics_spec.rb b/spec/migrations/schedule_repopulate_historical_vulnerability_statistics_spec.rb
new file mode 100644
index 00000000000..a65c94cf60e
--- /dev/null
+++ b/spec/migrations/schedule_repopulate_historical_vulnerability_statistics_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe ScheduleRepopulateHistoricalVulnerabilityStatistics do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:project_settings) { table(:project_settings) }
+
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let!(:project_1) { projects.create!(namespace_id: namespace.id, name: 'foo_1') }
+ let!(:project_2) { projects.create!(namespace_id: namespace.id, name: 'foo_2') }
+ let!(:project_3) { projects.create!(namespace_id: namespace.id, name: 'foo_3') }
+ let!(:project_4) { projects.create!(namespace_id: namespace.id, name: 'foo_4') }
+
+ around do |example|
+ freeze_time { Sidekiq::Testing.fake! { example.run } }
+ end
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+
+ project_settings.create!(project_id: project_1.id, has_vulnerabilities: true)
+ project_settings.create!(project_id: project_2.id, has_vulnerabilities: false)
+ project_settings.create!(project_id: project_4.id, has_vulnerabilities: true)
+ end
+
+ it 'schedules the background jobs', :aggregate_failures do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to be(2)
+ expect(described_class::MIGRATION_CLASS).to be_scheduled_delayed_migration(described_class::DELAY_INTERVAL, [project_1.id], described_class::DAY_COUNT)
+ expect(described_class::MIGRATION_CLASS).to be_scheduled_delayed_migration(2 * described_class::DELAY_INTERVAL, [project_4.id], described_class::DAY_COUNT)
+ end
+end
diff --git a/spec/migrations/schedule_update_existing_users_that_require_two_factor_auth_spec.rb b/spec/migrations/schedule_update_existing_users_that_require_two_factor_auth_spec.rb
new file mode 100644
index 00000000000..74f24906e41
--- /dev/null
+++ b/spec/migrations/schedule_update_existing_users_that_require_two_factor_auth_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20201030121314_schedule_update_existing_users_that_require_two_factor_auth.rb')
+
+RSpec.describe ScheduleUpdateExistingUsersThatRequireTwoFactorAuth do
+ let(:users) { table(:users) }
+ let!(:user_1) { users.create!(require_two_factor_authentication_from_group: true, name: "user1", email: "user1@example.com", projects_limit: 1) }
+ let!(:user_2) { users.create!(require_two_factor_authentication_from_group: false, name: "user2", email: "user2@example.com", projects_limit: 1) }
+ let!(:user_3) { users.create!(require_two_factor_authentication_from_group: true, name: "user3", email: "user3@example.com", projects_limit: 1) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+ end
+
+ it 'schedules jobs for users that require two factor authentication' do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 2.minutes, user_1.id, user_1.id)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 4.minutes, user_3.id, user_3.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/seed_repository_storages_weighted_spec.rb b/spec/migrations/seed_repository_storages_weighted_spec.rb
index fb077a3e345..d2fb1f7a014 100644
--- a/spec/migrations/seed_repository_storages_weighted_spec.rb
+++ b/spec/migrations/seed_repository_storages_weighted_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe SeedRepositoryStoragesWeighted do
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
- let(:application_setting) { application_settings.create }
+ let(:application_setting) { application_settings.create! }
let(:repository_storages) { ["foo"] }
it 'correctly schedules background migrations' do
diff --git a/spec/migrations/update_internal_ids_last_value_for_epics_renamed_spec.rb b/spec/migrations/update_internal_ids_last_value_for_epics_renamed_spec.rb
new file mode 100644
index 00000000000..71dd3a53bcd
--- /dev/null
+++ b/spec/migrations/update_internal_ids_last_value_for_epics_renamed_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20201208081429_update_internal_ids_last_value_for_epics_renamed.rb')
+
+RSpec.describe UpdateInternalIdsLastValueForEpicsRenamed, :migration, schema: 20201124185639 do
+ let(:namespaces) { table(:namespaces) }
+ let(:users) { table(:users) }
+ let(:epics) { table(:epics) }
+ let(:internal_ids) { table(:internal_ids) }
+
+ let!(:author) { users.create!(name: 'test', email: 'test@example.com', projects_limit: 0) }
+ let!(:group1) { namespaces.create!(type: 'Group', name: 'group1', path: 'group1') }
+ let!(:group2) { namespaces.create!(type: 'Group', name: 'group2', path: 'group2') }
+ let!(:group3) { namespaces.create!(type: 'Group', name: 'group3', path: 'group3') }
+ let!(:epic_last_value1) { internal_ids.create!(usage: 4, last_value: 5, namespace_id: group1.id) }
+ let!(:epic_last_value2) { internal_ids.create!(usage: 4, last_value: 5, namespace_id: group2.id) }
+ let!(:epic_last_value3) { internal_ids.create!(usage: 4, last_value: 5, namespace_id: group3.id) }
+ let!(:epic_1) { epics.create!(iid: 110, title: 'from epic 1', group_id: group1.id, author_id: author.id, title_html: 'any') }
+ let!(:epic_2) { epics.create!(iid: 5, title: 'from epic 1', group_id: group2.id, author_id: author.id, title_html: 'any') }
+ let!(:epic_3) { epics.create!(iid: 3, title: 'from epic 1', group_id: group3.id, author_id: author.id, title_html: 'any') }
+
+ it 'updates out of sync internal_ids last_value' do
+ migrate!
+
+ expect(internal_ids.find_by(usage: 4, namespace_id: group1.id).last_value).to eq(110)
+ expect(internal_ids.find_by(usage: 4, namespace_id: group2.id).last_value).to eq(5)
+ expect(internal_ids.find_by(usage: 4, namespace_id: group3.id).last_value).to eq(5)
+ end
+end
diff --git a/spec/models/alert_management/alert_spec.rb b/spec/models/alert_management/alert_spec.rb
index b57062b5fc1..80a45b1c1be 100644
--- a/spec/models/alert_management/alert_spec.rb
+++ b/spec/models/alert_management/alert_spec.rb
@@ -99,7 +99,8 @@ RSpec.describe AlertManagement::Alert do
describe 'fingerprint' do
let_it_be(:fingerprint) { 'fingerprint' }
- let(:new_alert) { build(:alert_management_alert, fingerprint: fingerprint, project: project) }
+ let_it_be(:project3, refind: true) { create(:project) }
+ let(:new_alert) { build(:alert_management_alert, fingerprint: fingerprint, project: project3) }
subject { new_alert }
@@ -107,7 +108,7 @@ RSpec.describe AlertManagement::Alert do
context 'same project, various states' do
using RSpec::Parameterized::TableSyntax
- let_it_be(:existing_alert) { create(:alert_management_alert, fingerprint: fingerprint, project: project) }
+ let_it_be(:existing_alert, refind: true) { create(:alert_management_alert, fingerprint: fingerprint, project: project3) }
# We are only validating uniqueness for non-resolved alerts
where(:existing_status, :new_status, :valid) do
@@ -130,7 +131,7 @@ RSpec.describe AlertManagement::Alert do
end
with_them do
- let(:new_alert) { build(:alert_management_alert, new_status, fingerprint: fingerprint, project: project) }
+ let(:new_alert) { build(:alert_management_alert, new_status, fingerprint: fingerprint, project: project3) }
before do
existing_alert.change_status_to(existing_status)
diff --git a/spec/models/alert_management/http_integration_spec.rb b/spec/models/alert_management/http_integration_spec.rb
index a3e7b47c116..910df51801a 100644
--- a/spec/models/alert_management/http_integration_spec.rb
+++ b/spec/models/alert_management/http_integration_spec.rb
@@ -31,6 +31,54 @@ RSpec.describe AlertManagement::HttpIntegration do
it { is_expected.not_to validate_uniqueness_of(:endpoint_identifier).scoped_to(:project_id, :active) }
end
+
+ context 'payload_attribute_mapping' do
+ subject { build(:alert_management_http_integration, payload_attribute_mapping: attribute_mapping) }
+
+ context 'with valid JSON schema' do
+ let(:attribute_mapping) do
+ {
+ title: { path: %w(a b c), type: 'string' },
+ description: { path: %w(a), type: 'string' }
+ }
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'with invalid JSON schema' do
+ shared_examples 'is invalid record' do
+ it do
+ expect(subject).to be_invalid
+ expect(subject.errors.messages[:payload_attribute_mapping]).to eq(['must be a valid json schema'])
+ end
+ end
+
+ context 'when property is not an object' do
+ let(:attribute_mapping) do
+ { title: 'That is not a valid schema' }
+ end
+
+ it_behaves_like 'is invalid record'
+ end
+
+ context 'when property missing required attributes' do
+ let(:attribute_mapping) do
+ { title: { type: 'string' } }
+ end
+
+ it_behaves_like 'is invalid record'
+ end
+
+ context 'when property has extra attributes' do
+ let(:attribute_mapping) do
+ { title: { path: %w(a b c), type: 'string', extra: 'property' } }
+ end
+
+ it_behaves_like 'is invalid record'
+ end
+ end
+ end
end
describe '#token' do
diff --git a/spec/models/analytics/devops_adoption/segment_selection_spec.rb b/spec/models/analytics/devops_adoption/segment_selection_spec.rb
deleted file mode 100644
index 5866cbaa48e..00000000000
--- a/spec/models/analytics/devops_adoption/segment_selection_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Analytics::DevopsAdoption::SegmentSelection, type: :model do
- subject { build(:devops_adoption_segment_selection, :project) }
-
- describe 'validation' do
- let_it_be(:group) { create(:group) }
- let_it_be(:project) { create(:project) }
-
- it { is_expected.to validate_presence_of(:segment) }
-
- context do
- subject { create(:devops_adoption_segment_selection, :project, project: project) }
-
- it { is_expected.to validate_uniqueness_of(:project_id).scoped_to(:segment_id) }
- end
-
- context do
- subject { create(:devops_adoption_segment_selection, :group, group: group) }
-
- it { is_expected.to validate_uniqueness_of(:group_id).scoped_to(:segment_id) }
- end
-
- it 'project is required' do
- selection = build(:devops_adoption_segment_selection, project: nil, group: nil)
-
- selection.validate
-
- expect(selection.errors).to have_key(:project)
- end
-
- it 'project is not required when a group is given' do
- selection = build(:devops_adoption_segment_selection, :group, group: group)
-
- expect(selection).to be_valid
- end
-
- it 'does not allow group to be set when project is present' do
- selection = build(:devops_adoption_segment_selection)
-
- selection.group = group
- selection.project = project
-
- selection.validate
-
- expect(selection.errors[:group]).to eq([s_('DevopsAdoptionSegmentSelection|The selection cannot be configured for a project and for a group at the same time')])
- end
-
- context 'limit the number of segment selections' do
- let_it_be(:segment) { create(:devops_adoption_segment) }
-
- subject { build(:devops_adoption_segment_selection, segment: segment, project: project) }
-
- before do
- create(:devops_adoption_segment_selection, :project, segment: segment)
-
- stub_const("#{described_class}::ALLOWED_SELECTIONS_PER_SEGMENT", 1)
- end
-
- it 'shows validation error' do
- subject.validate
-
- expect(subject.errors[:segment]).to eq([s_('DevopsAdoptionSegment|The maximum number of selections has been reached')])
- end
- end
- end
-end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index efe62a1d086..ea03cbc3706 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -259,7 +259,18 @@ RSpec.describe ApplicationSetting do
it { is_expected.to allow_value('access-key-id-12').for(:eks_access_key_id) }
it { is_expected.not_to allow_value('a' * 129).for(:eks_access_key_id) }
it { is_expected.not_to allow_value('short-key').for(:eks_access_key_id) }
- it { is_expected.not_to allow_value(nil).for(:eks_access_key_id) }
+ it { is_expected.to allow_value(nil).for(:eks_access_key_id) }
+
+ it { is_expected.to allow_value('secret-access-key').for(:eks_secret_access_key) }
+ it { is_expected.to allow_value(nil).for(:eks_secret_access_key) }
+ end
+
+ context 'access key is specified' do
+ let(:eks_enabled) { true }
+
+ before do
+ setting.eks_access_key_id = '123456789012'
+ end
it { is_expected.to allow_value('secret-access-key').for(:eks_secret_access_key) }
it { is_expected.not_to allow_value(nil).for(:eks_secret_access_key) }
diff --git a/spec/models/bulk_imports/entity_spec.rb b/spec/models/bulk_imports/entity_spec.rb
index ad6e3ec6f30..0732b671729 100644
--- a/spec/models/bulk_imports/entity_spec.rb
+++ b/spec/models/bulk_imports/entity_spec.rb
@@ -82,4 +82,68 @@ RSpec.describe BulkImports::Entity, type: :model do
end
end
end
+
+ describe "#update_tracker_for" do
+ let(:entity) { create(:bulk_import_entity) }
+
+ it "inserts new tracker when it does not exist" do
+ expect do
+ entity.update_tracker_for(relation: :relation, has_next_page: false)
+ end.to change(BulkImports::Tracker, :count).by(1)
+
+ tracker = entity.trackers.last
+
+ expect(tracker.relation).to eq('relation')
+ expect(tracker.has_next_page).to eq(false)
+ expect(tracker.next_page).to eq(nil)
+ end
+
+ it "updates the tracker if it already exist" do
+ create(
+ :bulk_import_tracker,
+ relation: :relation,
+ has_next_page: false,
+ entity: entity
+ )
+
+ expect do
+ entity.update_tracker_for(relation: :relation, has_next_page: true, next_page: 'nextPage')
+ end.not_to change(BulkImports::Tracker, :count)
+
+ tracker = entity.trackers.last
+
+ expect(tracker.relation).to eq('relation')
+ expect(tracker.has_next_page).to eq(true)
+ expect(tracker.next_page).to eq('nextPage')
+ end
+ end
+
+ describe "#has_next_page?" do
+ it "queries for the given relation if it has more pages to be fetched" do
+ entity = create(:bulk_import_entity)
+ create(
+ :bulk_import_tracker,
+ relation: :relation,
+ has_next_page: false,
+ entity: entity
+ )
+
+ expect(entity.has_next_page?(:relation)).to eq(false)
+ end
+ end
+
+ describe "#next_page_for" do
+ it "queries for the next page of the given relation" do
+ entity = create(:bulk_import_entity)
+ create(
+ :bulk_import_tracker,
+ relation: :relation,
+ has_next_page: false,
+ next_page: 'nextPage',
+ entity: entity
+ )
+
+ expect(entity.next_page_for(:relation)).to eq('nextPage')
+ end
+ end
end
diff --git a/spec/models/bulk_imports/failure_spec.rb b/spec/models/bulk_imports/failure_spec.rb
new file mode 100644
index 00000000000..cde62659a48
--- /dev/null
+++ b/spec/models/bulk_imports/failure_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Failure, type: :model do
+ describe 'associations' do
+ it { is_expected.to belong_to(:entity).required }
+ end
+
+ describe 'validations' do
+ before do
+ create(:bulk_import_failure)
+ end
+
+ it { is_expected.to validate_presence_of(:entity) }
+ end
+end
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index 51e82061d97..11dcecd50ca 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -330,14 +330,6 @@ RSpec.describe Ci::Bridge do
subject { build_stubbed(:ci_bridge, :manual).playable? }
it { is_expected.to be_truthy }
-
- context 'when FF ci_manual_bridges is disabled' do
- before do
- stub_feature_flags(ci_manual_bridges: false)
- end
-
- it { is_expected.to be_falsey }
- end
end
context 'when build is not a manual action' do
@@ -352,14 +344,6 @@ RSpec.describe Ci::Bridge do
subject { build_stubbed(:ci_bridge, :manual).action? }
it { is_expected.to be_truthy }
-
- context 'when FF ci_manual_bridges is disabled' do
- before do
- stub_feature_flags(ci_manual_bridges: false)
- end
-
- it { is_expected.to be_falsey }
- end
end
context 'when build is not a manual action' do
diff --git a/spec/models/ci/build_dependencies_spec.rb b/spec/models/ci/build_dependencies_spec.rb
index 4fa1b3eb5a5..c5f56dbe5bc 100644
--- a/spec/models/ci/build_dependencies_spec.rb
+++ b/spec/models/ci/build_dependencies_spec.rb
@@ -146,6 +146,204 @@ RSpec.describe Ci::BuildDependencies do
end
end
+ describe '#cross_pipeline' do
+ let!(:job) do
+ create(:ci_build,
+ pipeline: pipeline,
+ name: 'build_with_pipeline_dependency',
+ options: { cross_dependencies: dependencies })
+ end
+
+ subject { described_class.new(job) }
+
+ let(:cross_pipeline_deps) { subject.cross_pipeline }
+
+ context 'when dependency specifications are valid' do
+ context 'when pipeline exists in the hierarchy' do
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+ let!(:parent_pipeline) { create(:ci_pipeline, project: project) }
+
+ context 'when job exists' do
+ let(:dependencies) do
+ [{ pipeline: parent_pipeline.id.to_s, job: upstream_job.name, artifacts: true }]
+ end
+
+ let!(:upstream_job) { create(:ci_build, :success, pipeline: parent_pipeline) }
+
+ it { expect(cross_pipeline_deps).to contain_exactly(upstream_job) }
+ it { is_expected.to be_valid }
+
+ context 'when pipeline and job are specified via variables' do
+ let(:dependencies) do
+ [{ pipeline: '$parent_pipeline_ID', job: '$UPSTREAM_JOB', artifacts: true }]
+ end
+
+ before do
+ job.yaml_variables.push(key: 'parent_pipeline_ID', value: parent_pipeline.id.to_s, public: true)
+ job.yaml_variables.push(key: 'UPSTREAM_JOB', value: upstream_job.name, public: true)
+ job.save!
+ end
+
+ it { expect(cross_pipeline_deps).to contain_exactly(upstream_job) }
+ it { is_expected.to be_valid }
+ end
+
+ context 'when feature flag `ci_cross_pipeline_artifacts_download` is disabled' do
+ before do
+ stub_feature_flags(ci_cross_pipeline_artifacts_download: false)
+ end
+
+ it { expect(cross_pipeline_deps).to be_empty }
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'when same job names exist in other pipelines in the hierarchy' do
+ let(:cross_pipeline_limit) do
+ ::Gitlab::Ci::Config::Entry::Needs::NEEDS_CROSS_PIPELINE_DEPENDENCIES_LIMIT
+ end
+
+ let(:sibling_pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+
+ before do
+ cross_pipeline_limit.times do |index|
+ create(:ci_build, :success,
+ pipeline: parent_pipeline, name: "dependency-#{index}",
+ stage_idx: 1, stage: 'build', user: user
+ )
+
+ create(:ci_build, :success,
+ pipeline: sibling_pipeline, name: "dependency-#{index}",
+ stage_idx: 1, stage: 'build', user: user
+ )
+ end
+ end
+
+ let(:dependencies) do
+ [
+ { pipeline: parent_pipeline.id.to_s, job: 'dependency-0', artifacts: true },
+ { pipeline: parent_pipeline.id.to_s, job: 'dependency-1', artifacts: true },
+ { pipeline: parent_pipeline.id.to_s, job: 'dependency-2', artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: 'dependency-3', artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: 'dependency-4', artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: 'dependency-5', artifacts: true }
+ ]
+ end
+
+ it 'returns a limited number of dependencies with the right match' do
+ expect(job.options[:cross_dependencies].size).to eq(cross_pipeline_limit.next)
+ expect(cross_pipeline_deps.size).to eq(cross_pipeline_limit)
+ expect(cross_pipeline_deps.map { |dep| [dep.pipeline_id, dep.name] }).to contain_exactly(
+ [parent_pipeline.id, 'dependency-0'],
+ [parent_pipeline.id, 'dependency-1'],
+ [parent_pipeline.id, 'dependency-2'],
+ [sibling_pipeline.id, 'dependency-3'],
+ [sibling_pipeline.id, 'dependency-4'])
+ end
+ end
+
+ context 'when job does not exist' do
+ let(:dependencies) do
+ [{ pipeline: parent_pipeline.id.to_s, job: 'non-existent', artifacts: true }]
+ end
+
+ it { expect(cross_pipeline_deps).to be_empty }
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ context 'when pipeline does not exist' do
+ let(:dependencies) do
+ [{ pipeline: '123', job: 'non-existent', artifacts: true }]
+ end
+
+ it { expect(cross_pipeline_deps).to be_empty }
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when jobs exist in different pipelines in the hierarchy' do
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+ let!(:parent_pipeline) { create(:ci_pipeline, project: project) }
+ let!(:parent_job) { create(:ci_build, :success, name: 'parent_job', pipeline: parent_pipeline) }
+
+ let!(:sibling_pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+ let!(:sibling_job) { create(:ci_build, :success, name: 'sibling_job', pipeline: sibling_pipeline) }
+
+ context 'when pipeline and jobs dependencies are mismatched' do
+ let(:dependencies) do
+ [
+ { pipeline: parent_pipeline.id.to_s, job: sibling_job.name, artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: parent_job.name, artifacts: true }
+ ]
+ end
+
+ it { expect(cross_pipeline_deps).to be_empty }
+ it { is_expected.not_to be_valid }
+
+ context 'when dependencies contain a valid pair' do
+ let(:dependencies) do
+ [
+ { pipeline: parent_pipeline.id.to_s, job: sibling_job.name, artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: parent_job.name, artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: sibling_job.name, artifacts: true }
+ ]
+ end
+
+ it 'filters out the invalid ones' do
+ expect(cross_pipeline_deps).to contain_exactly(sibling_job)
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+ end
+
+ context 'when job and pipeline exist outside the hierarchy' do
+ let!(:pipeline) { create(:ci_pipeline, project: project) }
+ let!(:another_pipeline) { create(:ci_pipeline, project: project) }
+ let!(:dependency) { create(:ci_build, :success, pipeline: another_pipeline) }
+
+ let(:dependencies) do
+ [{ pipeline: another_pipeline.id.to_s, job: dependency.name, artifacts: true }]
+ end
+
+ it 'ignores jobs outside the pipeline hierarchy' do
+ expect(cross_pipeline_deps).to be_empty
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when current pipeline is specified' do
+ let!(:pipeline) { create(:ci_pipeline, project: project) }
+ let!(:dependency) { create(:ci_build, :success, pipeline: pipeline) }
+
+ let(:dependencies) do
+ [{ pipeline: pipeline.id.to_s, job: dependency.name, artifacts: true }]
+ end
+
+ it 'ignores jobs from the current pipeline as simple needs should be used instead' do
+ expect(cross_pipeline_deps).to be_empty
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ context 'when artifacts:false' do
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+ let!(:parent_pipeline) { create(:ci_pipeline, project: project) }
+ let!(:parent_job) { create(:ci_build, :success, name: 'parent_job', pipeline: parent_pipeline) }
+
+ let(:dependencies) do
+ [{ pipeline: parent_pipeline.id.to_s, job: parent_job.name, artifacts: false }]
+ end
+
+ it { expect(cross_pipeline_deps).to be_empty }
+ it { is_expected.to be_valid } # we simply ignore it
+ end
+ end
+
describe '#all' do
let!(:job) do
create(:ci_build, pipeline: pipeline, name: 'deploy', stage_idx: 3, stage: 'deploy')
@@ -155,9 +353,9 @@ RSpec.describe Ci::BuildDependencies do
subject { dependencies.all }
- it 'returns the union of all local dependencies and any cross pipeline dependencies' do
+ it 'returns the union of all local dependencies and any cross project dependencies' do
expect(dependencies).to receive(:local).and_return([1, 2, 3])
- expect(dependencies).to receive(:cross_pipeline).and_return([3, 4])
+ expect(dependencies).to receive(:cross_project).and_return([3, 4])
expect(subject).to contain_exactly(1, 2, 3, 4)
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 5ff9b4dd493..9f412d64d56 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Ci::Build do
status: 'success')
end
- let(:build) { create(:ci_build, pipeline: pipeline) }
+ let_it_be(:build, refind: true) { create(:ci_build, pipeline: pipeline) }
it { is_expected.to belong_to(:runner) }
it { is_expected.to belong_to(:trigger_request) }
@@ -307,8 +307,6 @@ RSpec.describe Ci::Build do
end
describe '.without_needs' do
- let!(:build) { create(:ci_build) }
-
subject { described_class.without_needs }
context 'when no build_need is created' do
@@ -1151,12 +1149,26 @@ RSpec.describe Ci::Build do
end
context 'when transits to skipped' do
- before do
- build.skip!
+ context 'when cd_skipped_deployment_status is disabled' do
+ before do
+ stub_feature_flags(cd_skipped_deployment_status: false)
+ build.skip!
+ end
+
+ it 'transits deployment status to canceled' do
+ expect(deployment).to be_canceled
+ end
end
- it 'transits deployment status to canceled' do
- expect(deployment).to be_canceled
+ context 'when cd_skipped_deployment_status is enabled' do
+ before do
+ stub_feature_flags(cd_skipped_deployment_status: project)
+ build.skip!
+ end
+
+ it 'transits deployment status to skipped' do
+ expect(deployment).to be_skipped
+ end
end
end
@@ -2005,6 +2017,8 @@ RSpec.describe Ci::Build do
end
context 'when ci_build_metadata_config is disabled' do
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
before do
stub_feature_flags(ci_build_metadata_config: false)
end
@@ -2392,6 +2406,7 @@ RSpec.describe Ci::Build do
before do
stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com')
+ stub_config(dependency_proxy: { enabled: true })
end
subject { build.variables }
@@ -2409,6 +2424,8 @@ RSpec.describe Ci::Build do
{ key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false },
{ key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: true },
{ key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false },
+ { key: 'CI_DEPENDENCY_PROXY_USER', value: 'gitlab-ci-token', public: true, masked: false },
+ { key: 'CI_DEPENDENCY_PROXY_PASSWORD', value: 'my-token', public: false, masked: true },
{ key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true },
{ key: 'CI_JOB_NAME', value: 'test', public: true, masked: false },
{ key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false },
@@ -2441,6 +2458,11 @@ RSpec.describe Ci::Build do
{ key: 'CI_DEFAULT_BRANCH', value: project.default_branch, public: true, masked: false },
{ key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false },
{ key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
+ { key: 'CI_DEPENDENCY_PROXY_SERVER', value: "#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}", public: true, masked: false },
+ { key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX',
+ value: "#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/#{project.namespace.root_ancestor.path}#{DependencyProxy::URL_SUFFIX}",
+ public: true,
+ masked: false },
{ key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
{ key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false },
{ key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false },
@@ -2502,6 +2524,7 @@ RSpec.describe Ci::Build do
let(:project_pre_var) { { key: 'project', value: 'value', public: true, masked: false } }
let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true, masked: false } }
let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } }
+ let(:dependency_proxy_var) { { key: 'dependency_proxy', value: 'value', public: true, masked: false } }
let(:job_jwt_var) { { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true } }
let(:job_dependency_var) { { key: 'job_dependency', value: 'value', public: true, masked: false } }
@@ -2511,6 +2534,7 @@ RSpec.describe Ci::Build do
allow(build).to receive(:persisted_variables) { [] }
allow(build).to receive(:job_jwt_variables) { [job_jwt_var] }
allow(build).to receive(:dependency_variables) { [job_dependency_var] }
+ allow(build).to receive(:dependency_proxy_variables) { [dependency_proxy_var] }
allow(build.project)
.to receive(:predefined_variables) { [project_pre_var] }
@@ -2523,7 +2547,8 @@ RSpec.describe Ci::Build do
it 'returns variables in order depending on resource hierarchy' do
is_expected.to eq(
- [job_jwt_var,
+ [dependency_proxy_var,
+ job_jwt_var,
build_pre_var,
project_pre_var,
pipeline_pre_var,
@@ -2730,7 +2755,11 @@ RSpec.describe Ci::Build do
pipeline.update!(tag: true)
end
- it { is_expected.to include(tag_variable) }
+ it do
+ build.reload
+
+ expect(subject).to include(tag_variable)
+ end
end
context 'when CI variable is defined' do
@@ -2964,8 +2993,11 @@ RSpec.describe Ci::Build do
end
context 'when pipeline variable overrides build variable' do
+ let(:build) do
+ create(:ci_build, pipeline: pipeline, yaml_variables: [{ key: 'MYVAR', value: 'myvar', public: true }])
+ end
+
before do
- build.yaml_variables = [{ key: 'MYVAR', value: 'myvar', public: true }]
pipeline.variables.build(key: 'MYVAR', value: 'pipeline value')
end
@@ -3281,9 +3313,12 @@ RSpec.describe Ci::Build do
end
context 'when overriding user-provided variables' do
+ let(:build) do
+ create(:ci_build, pipeline: pipeline, yaml_variables: [{ key: 'MY_VAR', value: 'myvar', public: true }])
+ end
+
before do
pipeline.variables.build(key: 'MY_VAR', value: 'pipeline value')
- build.yaml_variables = [{ key: 'MY_VAR', value: 'myvar', public: true }]
end
it 'returns a hash including variable with higher precedence' do
@@ -3640,7 +3675,7 @@ RSpec.describe Ci::Build do
end
it 'handles raised exception' do
- expect { subject.drop! }.not_to raise_exception(Gitlab::Access::AccessDeniedError)
+ expect { subject.drop! }.not_to raise_error
end
it 'logs the error' do
@@ -4045,13 +4080,72 @@ RSpec.describe Ci::Build do
end
end
+ context 'when there is a Cobertura coverage report with class filename paths not relative to project root' do
+ before do
+ allow(build.project).to receive(:full_path).and_return('root/javademo')
+ allow(build.pipeline).to receive(:all_worktree_paths).and_return(['src/main/java/com/example/javademo/User.java'])
+
+ create(:ci_job_artifact, :coverage_with_paths_not_relative_to_project_root, job: build, project: build.project)
+ end
+
+ it 'parses blobs and add the results to the coverage report with corrected paths' do
+ expect { subject }.not_to raise_error
+
+ expect(coverage_report.files.keys).to match_array(['src/main/java/com/example/javademo/User.java'])
+ end
+
+ context 'and smart_cobertura_parser feature flag is disabled' do
+ before do
+ stub_feature_flags(smart_cobertura_parser: false)
+ end
+
+ it 'parses blobs and add the results to the coverage report with unmodified paths' do
+ expect { subject }.not_to raise_error
+
+ expect(coverage_report.files.keys).to match_array(['com/example/javademo/User.java'])
+ end
+ end
+ end
+
context 'when there is a corrupted Cobertura coverage report' do
before do
create(:ci_job_artifact, :coverage_with_corrupted_data, job: build, project: build.project)
end
it 'raises an error' do
- expect { subject }.to raise_error(Gitlab::Ci::Parsers::Coverage::Cobertura::CoberturaParserError)
+ expect { subject }.to raise_error(Gitlab::Ci::Parsers::Coverage::Cobertura::InvalidLineInformationError)
+ end
+ end
+ end
+ end
+
+ describe '#collect_codequality_reports!' do
+ subject(:codequality_report) { build.collect_codequality_reports!(Gitlab::Ci::Reports::CodequalityReports.new) }
+
+ it { expect(codequality_report.degradations).to eq({}) }
+
+ context 'when build has a codequality report' do
+ context 'when there is a codequality report' do
+ before do
+ create(:ci_job_artifact, :codequality, job: build, project: build.project)
+ end
+
+ it 'parses blobs and add the results to the codequality report' do
+ expect { codequality_report }.not_to raise_error
+
+ expect(codequality_report.degradations_count).to eq(3)
+ end
+ end
+
+ context 'when there is an codequality report without errors' do
+ before do
+ create(:ci_job_artifact, :codequality_without_errors, job: build, project: build.project)
+ end
+
+ it 'parses blobs and add the results to the codequality report' do
+ expect { codequality_report }.not_to raise_error
+
+ expect(codequality_report.degradations_count).to eq(0)
end
end
end
@@ -4639,4 +4733,78 @@ RSpec.describe Ci::Build do
expect(action).not_to have_received(:perform!)
end
end
+
+ describe '#debug_mode?' do
+ subject { build.debug_mode? }
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(restrict_access_to_build_debug_mode: false)
+ end
+
+ it { is_expected.to eq false }
+
+ context 'when in variables' do
+ before do
+ create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true')
+ end
+
+ it { is_expected.to eq false }
+ end
+ end
+
+ context 'when CI_DEBUG_TRACE=true is in variables' do
+ context 'when in instance variables' do
+ before do
+ create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true')
+ end
+
+ it { is_expected.to eq true }
+ end
+
+ context 'when in group variables' do
+ before do
+ create(:ci_group_variable, key: 'CI_DEBUG_TRACE', value: 'true', group: project.group)
+ end
+
+ it { is_expected.to eq true }
+ end
+
+ context 'when in pipeline variables' do
+ before do
+ create(:ci_pipeline_variable, key: 'CI_DEBUG_TRACE', value: 'true', pipeline: pipeline)
+ end
+
+ it { is_expected.to eq true }
+ end
+
+ context 'when in project variables' do
+ before do
+ create(:ci_variable, key: 'CI_DEBUG_TRACE', value: 'true', project: project)
+ end
+
+ it { is_expected.to eq true }
+ end
+
+ context 'when in job variables' do
+ before do
+ create(:ci_job_variable, key: 'CI_DEBUG_TRACE', value: 'true', job: build)
+ end
+
+ it { is_expected.to eq true }
+ end
+
+ context 'when in yaml variables' do
+ before do
+ build.update!(yaml_variables: [{ key: :CI_DEBUG_TRACE, value: 'true' }])
+ end
+
+ it { is_expected.to eq true }
+ end
+ end
+
+ context 'when CI_DEBUG_TRACE is not in variables' do
+ it { is_expected.to eq false }
+ end
+ end
end
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index dce7b1d30ca..75ed5939724 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -91,7 +91,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end
describe 'CHUNK_SIZE' do
- it 'Chunk size can not be changed without special care' do
+ it 'chunk size can not be changed without special care' do
expect(described_class::CHUNK_SIZE).to eq(128.kilobytes)
end
end
diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb
index 20ca0c8b710..bc96e2584cf 100644
--- a/spec/models/ci/build_trace_chunks/fog_spec.rb
+++ b/spec/models/ci/build_trace_chunks/fog_spec.rb
@@ -74,6 +74,52 @@ RSpec.describe Ci::BuildTraceChunks::Fog do
expect(data_store.data(model)).to eq new_data
end
+
+ context 'when S3 server side encryption is enabled' do
+ before do
+ config = Gitlab.config.artifacts.object_store.to_h
+ config[:storage_options] = { server_side_encryption: 'AES256' }
+ allow(data_store).to receive(:object_store_raw_config).and_return(config)
+ end
+
+ it 'creates a file with attributes' do
+ expect_next_instance_of(Fog::AWS::Storage::Files) do |files|
+ expect(files).to receive(:create).with(
+ hash_including(
+ key: anything,
+ body: new_data,
+ 'x-amz-server-side-encryption' => 'AES256')
+ ).and_call_original
+ end
+
+ expect(data_store.data(model)).to be_nil
+
+ data_store.set_data(model, new_data)
+
+ expect(data_store.data(model)).to eq new_data
+ end
+
+ context 'when ci_live_trace_use_fog_attributes flag is disabled' do
+ before do
+ stub_feature_flags(ci_live_trace_use_fog_attributes: false)
+ end
+
+ it 'does not pass along Fog attributes' do
+ expect_next_instance_of(Fog::AWS::Storage::Files) do |files|
+ expect(files).to receive(:create).with(
+ key: anything,
+ body: new_data
+ ).and_call_original
+ end
+
+ expect(data_store.data(model)).to be_nil
+
+ data_store.set_data(model, new_data)
+
+ expect(data_store.data(model)).to eq new_data
+ end
+ end
+ end
end
end
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 26851c93ac3..ef21ca8f100 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -96,6 +96,22 @@ RSpec.describe Ci::JobArtifact do
end
end
+ describe '.codequality_reports' do
+ subject { described_class.codequality_reports }
+
+ context 'when there is a codequality report' do
+ let!(:artifact) { create(:ci_job_artifact, :codequality) }
+
+ it { is_expected.to eq([artifact]) }
+ end
+
+ context 'when there are no codequality reports' do
+ let!(:artifact) { create(:ci_job_artifact, :archive) }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
describe '.terraform_reports' do
context 'when there is a terraform report' do
it 'return the job artifact' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 1ca370dc950..f5e824bb066 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -222,6 +222,26 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '.for_branch' do
+ subject { described_class.for_branch(branch) }
+
+ let(:branch) { 'master' }
+ let!(:pipeline) { create(:ci_pipeline, ref: 'master') }
+
+ it 'returns the pipeline' do
+ is_expected.to contain_exactly(pipeline)
+ end
+
+ context 'with tag pipeline' do
+ let(:branch) { 'v1.0' }
+ let!(:pipeline) { create(:ci_pipeline, ref: 'v1.0', tag: true) }
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '.ci_sources' do
subject { described_class.ci_sources }
@@ -242,6 +262,27 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '.ci_branch_sources' do
+ subject { described_class.ci_branch_sources }
+
+ let_it_be(:push_pipeline) { create(:ci_pipeline, source: :push) }
+ let_it_be(:web_pipeline) { create(:ci_pipeline, source: :web) }
+ let_it_be(:api_pipeline) { create(:ci_pipeline, source: :api) }
+ let_it_be(:webide_pipeline) { create(:ci_pipeline, source: :webide) }
+ let_it_be(:child_pipeline) { create(:ci_pipeline, source: :parent_pipeline) }
+ let_it_be(:merge_request_pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline) }
+
+ it 'contains pipelines having CI only sources' do
+ expect(subject).to contain_exactly(push_pipeline, web_pipeline, api_pipeline)
+ end
+
+ it 'filters on expected sources' do
+ expect(::Enums::Ci::Pipeline.ci_branch_sources.keys).to contain_exactly(
+ *%i[unknown push web trigger schedule api external pipeline chat
+ external_pull_request_event])
+ end
+ end
+
describe '.outside_pipeline_family' do
subject(:outside_pipeline_family) { described_class.outside_pipeline_family(upstream_pipeline) }
@@ -269,7 +310,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let!(:older_other_pipeline) { create(:ci_pipeline, project: project) }
let!(:upstream_pipeline) { create(:ci_pipeline, project: project) }
- let!(:child_pipeline) { create(:ci_pipeline, project: project) }
+ let!(:child_pipeline) { create(:ci_pipeline, child_of: upstream_pipeline) }
let!(:other_pipeline) { create(:ci_pipeline, project: project) }
@@ -498,6 +539,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ context 'when pipeline has a codequality report' do
+ subject { described_class.with_reports(Ci::JobArtifact.codequality_reports) }
+
+ let(:pipeline_with_report) { create(:ci_pipeline, :with_codequality_reports) }
+
+ it 'selects the pipeline' do
+ is_expected.to eq([pipeline_with_report])
+ end
+ end
+
context 'when pipeline has a terraform report' do
it 'selects the pipeline' do
pipeline_with_report = create(:ci_pipeline, :with_terraform_reports)
@@ -744,11 +795,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
]
end
- context 'when pipeline is merge request' do
- let(:pipeline) do
- create(:ci_pipeline, merge_request: merge_request)
- end
-
+ context 'when merge request is present' do
let(:merge_request) do
create(:merge_request,
source_project: project,
@@ -764,64 +811,142 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let(:milestone) { create(:milestone, project: project) }
let(:labels) { create_list(:label, 2) }
- it 'exposes merge request pipeline variables' do
- expect(subject.to_hash)
- .to include(
- 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s,
- 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s,
- 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s,
- 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s,
- 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
- 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
- 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
- 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => pipeline.target_sha.to_s,
- 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
- 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
- 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
- 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
- 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s,
- 'CI_MERGE_REQUEST_TITLE' => merge_request.title,
- 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
- 'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
- 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
- 'CI_MERGE_REQUEST_EVENT_TYPE' => pipeline.merge_request_event_type.to_s)
- end
-
- context 'when source project does not exist' do
+ context 'when pipeline for merge request is created' do
+ let(:pipeline) do
+ create(:ci_pipeline, :detached_merge_request_pipeline,
+ ci_ref_presence: false,
+ user: user,
+ merge_request: merge_request)
+ end
+
before do
- merge_request.update_column(:source_project_id, nil)
+ project.add_developer(user)
end
- it 'does not expose source project related variables' do
- expect(subject.to_hash.keys).not_to include(
- %w[CI_MERGE_REQUEST_SOURCE_PROJECT_ID
- CI_MERGE_REQUEST_SOURCE_PROJECT_PATH
- CI_MERGE_REQUEST_SOURCE_PROJECT_URL
- CI_MERGE_REQUEST_SOURCE_BRANCH_NAME])
+ it 'exposes merge request pipeline variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s,
+ 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s,
+ 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
+ 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => '',
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => '',
+ 'CI_MERGE_REQUEST_TITLE' => merge_request.title,
+ 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
+ 'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
+ 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
+ 'CI_MERGE_REQUEST_EVENT_TYPE' => 'detached',
+ 'CI_OPEN_MERGE_REQUESTS' => merge_request.to_reference(full: true))
+ end
+
+ it 'exposes diff variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DIFF_ID' => merge_request.merge_request_diff.id.to_s,
+ 'CI_MERGE_REQUEST_DIFF_BASE_SHA' => merge_request.merge_request_diff.base_commit_sha)
+ end
+
+ context 'without assignee' do
+ let(:assignees) { [] }
+
+ it 'does not expose assignee variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_ASSIGNEES')
+ end
end
- end
- context 'without assignee' do
- let(:assignees) { [] }
+ context 'without milestone' do
+ let(:milestone) { nil }
- it 'does not expose assignee variable' do
- expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_ASSIGNEES')
+ it 'does not expose milestone variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_MILESTONE')
+ end
+ end
+
+ context 'without labels' do
+ let(:labels) { [] }
+
+ it 'does not expose labels variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_LABELS')
+ end
end
end
- context 'without milestone' do
- let(:milestone) { nil }
+ context 'when pipeline on branch is created' do
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, user: user, ref: 'feature')
+ end
+
+ context 'when a merge request is created' do
+ before do
+ merge_request
+ end
- it 'does not expose milestone variable' do
- expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_MILESTONE')
+ context 'when user has access to project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'merge request references are returned matching the pipeline' do
+ expect(subject.to_hash).to include(
+ 'CI_OPEN_MERGE_REQUESTS' => merge_request.to_reference(full: true))
+ end
+ end
+
+ context 'when user does not have access to project' do
+ it 'CI_OPEN_MERGE_REQUESTS is not returned' do
+ expect(subject.to_hash).not_to have_key('CI_OPEN_MERGE_REQUESTS')
+ end
+ end
+ end
+
+ context 'when no a merge request is created' do
+ it 'CI_OPEN_MERGE_REQUESTS is not returned' do
+ expect(subject.to_hash).not_to have_key('CI_OPEN_MERGE_REQUESTS')
+ end
end
end
- context 'without labels' do
- let(:labels) { [] }
+ context 'with merged results' do
+ let(:pipeline) do
+ create(:ci_pipeline, :merged_result_pipeline, merge_request: merge_request)
+ end
+
+ it 'exposes merge request pipeline variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s,
+ 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s,
+ 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
+ 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => merge_request.target_branch_sha,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => merge_request.source_branch_sha,
+ 'CI_MERGE_REQUEST_TITLE' => merge_request.title,
+ 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
+ 'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
+ 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
+ 'CI_MERGE_REQUEST_EVENT_TYPE' => 'merged_result')
+ end
- it 'does not expose labels variable' do
- expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_LABELS')
+ it 'exposes diff variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DIFF_ID' => merge_request.merge_request_diff.id.to_s,
+ 'CI_MERGE_REQUEST_DIFF_BASE_SHA' => merge_request.merge_request_diff.base_commit_sha)
end
end
end
@@ -1126,6 +1251,40 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe 'synching status to Jira' do
+ let(:worker) { ::JiraConnect::SyncBuildsWorker }
+
+ %i[prepare! run! skip! drop! succeed! cancel! block! delay!].each do |event|
+ context "when we call pipeline.#{event}" do
+ it 'triggers a Jira synch worker' do
+ expect(worker).to receive(:perform_async).with(pipeline.id, Integer)
+
+ pipeline.send(event)
+ end
+
+ context 'the feature is disabled' do
+ it 'does not trigger a worker' do
+ stub_feature_flags(jira_sync_builds: false)
+
+ expect(worker).not_to receive(:perform_async)
+
+ pipeline.send(event)
+ end
+ end
+
+ context 'the feature is enabled for this project' do
+ it 'does trigger a worker' do
+ stub_feature_flags(jira_sync_builds: pipeline.project)
+
+ expect(worker).to receive(:perform_async)
+
+ pipeline.send(event)
+ end
+ end
+ end
+ end
+ end
+
describe '#duration', :sidekiq_inline do
context 'when multiple builds are finished' do
before do
@@ -2539,6 +2698,14 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
it 'receives a pending event once' do
expect(WebMock).to have_requested_pipeline_hook('pending').once
end
+
+ it 'builds hook data once' do
+ create(:pipelines_email_service, project: project)
+
+ expect(Gitlab::DataBuilder::Pipeline).to receive(:build).once.and_call_original
+
+ pipeline.execute_hooks
+ end
end
context 'when build is run' do
@@ -2600,6 +2767,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
it 'did not execute pipeline_hook after touched' do
expect(WebMock).not_to have_requested(:post, hook.url)
end
+
+ it 'does not build hook data' do
+ expect(Gitlab::DataBuilder::Pipeline).not_to receive(:build)
+
+ pipeline.execute_hooks
+ end
end
def create_build(name, stage_idx)
@@ -2734,6 +2907,93 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '#related_merge_requests' do
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
+ let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'stable') }
+ let(:branch_pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
+ let(:merge_pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline, merge_request: merge_request) }
+
+ context 'for a branch pipeline' do
+ subject { branch_pipeline.related_merge_requests }
+
+ it 'when no merge request is created' do
+ is_expected.to be_empty
+ end
+
+ it 'when another merge requests are created' do
+ merge_request
+ other_merge_request
+
+ is_expected.to contain_exactly(merge_request, other_merge_request)
+ end
+ end
+
+ context 'for a merge pipeline' do
+ subject { merge_pipeline.related_merge_requests }
+
+ it 'when only merge pipeline is created' do
+ merge_pipeline
+
+ is_expected.to contain_exactly(merge_request)
+ end
+
+ it 'when a merge request is created' do
+ merge_pipeline
+ other_merge_request
+
+ is_expected.to contain_exactly(merge_request, other_merge_request)
+ end
+ end
+ end
+
+ describe '#open_merge_requests_refs' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let!(:pipeline) { create(:ci_pipeline, user: user, project: project, ref: 'feature') }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
+
+ subject { pipeline.open_merge_requests_refs }
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns open merge requests' do
+ is_expected.to eq([merge_request.to_reference(full: true)])
+ end
+
+ it 'does not return closed merge requests' do
+ merge_request.close!
+
+ is_expected.to be_empty
+ end
+
+ context 'limits amount of returned merge requests' do
+ let!(:other_merge_requests) do
+ Array.new(4) do |idx|
+ create(:merge_request, source_project: project, source_branch: 'feature', target_branch: "master-#{idx}")
+ end
+ end
+
+ let(:other_merge_requests_refs) do
+ other_merge_requests.map { |mr| mr.to_reference(full: true) }
+ end
+
+ it 'returns only last 4 in a reverse order' do
+ is_expected.to eq(other_merge_requests_refs.reverse)
+ end
+ end
+ end
+
+ context 'when user does not have permissions' do
+ it 'does not return any merge requests' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '#same_family_pipeline_ids' do
subject { pipeline.same_family_pipeline_ids.map(&:id) }
@@ -2744,13 +3004,9 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is child' do
- let(:parent) { create(:ci_pipeline, project: pipeline.project) }
- let(:sibling) { create(:ci_pipeline, project: pipeline.project) }
-
- before do
- create_source_pipeline(parent, pipeline)
- create_source_pipeline(parent, sibling)
- end
+ let(:parent) { create(:ci_pipeline, project: project) }
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent) }
+ let!(:sibling) { create(:ci_pipeline, child_of: parent) }
it 'returns parent sibling and self ids' do
expect(subject).to contain_exactly(parent.id, pipeline.id, sibling.id)
@@ -2758,11 +3014,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is parent' do
- let(:child) { create(:ci_pipeline, project: pipeline.project) }
-
- before do
- create_source_pipeline(pipeline, child)
- end
+ let!(:child) { create(:ci_pipeline, child_of: pipeline) }
it 'returns self and child ids' do
expect(subject).to contain_exactly(pipeline.id, child.id)
@@ -2770,17 +3022,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is a child of a child pipeline' do
- let(:ancestor) { create(:ci_pipeline, project: pipeline.project) }
- let(:parent) { create(:ci_pipeline, project: pipeline.project) }
- let(:cousin_parent) { create(:ci_pipeline, project: pipeline.project) }
- let(:cousin) { create(:ci_pipeline, project: pipeline.project) }
-
- before do
- create_source_pipeline(ancestor, parent)
- create_source_pipeline(ancestor, cousin_parent)
- create_source_pipeline(parent, pipeline)
- create_source_pipeline(cousin_parent, cousin)
- end
+ let(:ancestor) { create(:ci_pipeline, project: project) }
+ let!(:parent) { create(:ci_pipeline, child_of: ancestor) }
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent) }
+ let!(:cousin_parent) { create(:ci_pipeline, child_of: ancestor) }
+ let!(:cousin) { create(:ci_pipeline, child_of: cousin_parent) }
it 'returns all family ids' do
expect(subject).to contain_exactly(
@@ -2790,11 +3036,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is a triggered pipeline' do
- let(:upstream) { create(:ci_pipeline, project: create(:project)) }
-
- before do
- create_source_pipeline(upstream, pipeline)
- end
+ let!(:upstream) { create(:ci_pipeline, project: create(:project), upstream_of: pipeline)}
it 'returns self id' do
expect(subject).to contain_exactly(pipeline.id)
@@ -2802,6 +3044,46 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '#root_ancestor' do
+ subject { pipeline.root_ancestor }
+
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
+ context 'when pipeline is child of child pipeline' do
+ let!(:root_ancestor) { create(:ci_pipeline, project: project) }
+ let!(:parent_pipeline) { create(:ci_pipeline, child_of: root_ancestor) }
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+
+ it 'returns the root ancestor' do
+ expect(subject).to eq(root_ancestor)
+ end
+ end
+
+ context 'when pipeline is root ancestor' do
+ let!(:child_pipeline) { create(:ci_pipeline, child_of: pipeline) }
+
+ it 'returns itself' do
+ expect(subject).to eq(pipeline)
+ end
+ end
+
+ context 'when pipeline is standalone' do
+ it 'returns itself' do
+ expect(subject).to eq(pipeline)
+ end
+ end
+
+ context 'when pipeline is multi-project downstream pipeline' do
+ let!(:upstream_pipeline) do
+ create(:ci_pipeline, project: create(:project), upstream_of: pipeline)
+ end
+
+ it 'ignores cross project ancestors' do
+ expect(subject).to eq(pipeline)
+ end
+ end
+ end
+
describe '#stuck?' do
before do
create(:ci_build, :pending, pipeline: pipeline)
@@ -2838,7 +3120,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
stub_feature_flags(ci_store_pipeline_messages: false)
end
- it ' does not add pipeline error message' do
+ it 'does not add pipeline error message' do
pipeline.add_error_message('The error message')
expect(pipeline.messages).to be_empty
@@ -3343,6 +3625,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
])
end
+ it 'does not execute N+1 queries' do
+ single_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project)
+ single_rspec = create(:ci_build, :success, name: 'rspec', pipeline: single_build_pipeline, project: project)
+ create(:ci_job_artifact, :cobertura, job: single_rspec, project: project)
+
+ control = ActiveRecord::QueryRecorder.new { single_build_pipeline.coverage_reports }
+
+ expect { subject }.not_to exceed_query_limit(control)
+ end
+
context 'when builds are retried' do
let!(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) }
let!(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline, project: project) }
@@ -3360,6 +3652,39 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '#codequality_reports' do
+ subject(:codequality_reports) { pipeline.codequality_reports }
+
+ context 'when pipeline has multiple builds with codequality reports' do
+ let(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline, project: project) }
+ let(:build_golang) { create(:ci_build, :success, name: 'golang', pipeline: pipeline, project: project) }
+
+ before do
+ create(:ci_job_artifact, :codequality, job: build_rspec, project: project)
+ create(:ci_job_artifact, :codequality_without_errors, job: build_golang, project: project)
+ end
+
+ it 'returns codequality report with collected data' do
+ expect(codequality_reports.degradations_count).to eq(3)
+ end
+
+ context 'when builds are retried' do
+ let(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) }
+ let(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline, project: project) }
+
+ it 'returns a codequality reports without degradations' do
+ expect(codequality_reports.degradations).to be_empty
+ end
+ end
+ end
+
+ context 'when pipeline does not have any builds with codequality reports' do
+ it 'returns codequality reports without degradations' do
+ expect(codequality_reports.degradations).to be_empty
+ end
+ end
+ end
+
describe '#total_size' do
let!(:build_job1) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
let!(:build_job2) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
@@ -3509,18 +3834,9 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
describe '#parent_pipeline' do
let_it_be(:project) { create(:project) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
-
context 'when pipeline is triggered by a pipeline from the same project' do
- let(:upstream_pipeline) { create(:ci_pipeline, project: pipeline.project) }
-
- before do
- create(:ci_sources_pipeline,
- source_pipeline: upstream_pipeline,
- source_project: project,
- pipeline: pipeline,
- project: project)
- end
+ let_it_be(:upstream_pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:pipeline) { create(:ci_pipeline, child_of: upstream_pipeline) }
it 'returns the parent pipeline' do
expect(pipeline.parent_pipeline).to eq(upstream_pipeline)
@@ -3532,15 +3848,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is triggered by a pipeline from another project' do
- let(:upstream_pipeline) { create(:ci_pipeline) }
-
- before do
- create(:ci_sources_pipeline,
- source_pipeline: upstream_pipeline,
- source_project: upstream_pipeline.project,
- pipeline: pipeline,
- project: project)
- end
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let!(:upstream_pipeline) { create(:ci_pipeline, project: create(:project), upstream_of: pipeline) }
it 'returns nil' do
expect(pipeline.parent_pipeline).to be_nil
@@ -3552,6 +3861,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is not triggered by a pipeline' do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
it 'returns nil' do
expect(pipeline.parent_pipeline).to be_nil
end
@@ -3851,4 +4162,70 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
bridge
end
end
+
+ describe 'test failure history processing' do
+ it 'performs the service asynchronously when the pipeline is completed' do
+ service = double
+
+ expect(Ci::TestFailureHistoryService).to receive(:new).with(pipeline).and_return(service)
+ expect(service).to receive_message_chain(:async, :perform_if_needed)
+
+ pipeline.succeed!
+ end
+ end
+
+ describe '#latest_test_report_builds' do
+ it 'returns pipeline builds with test report artifacts' do
+ test_build = create(:ci_build, :test_reports, pipeline: pipeline, project: project)
+ create(:ci_build, :artifacts, pipeline: pipeline, project: project)
+
+ expect(pipeline.latest_test_report_builds).to contain_exactly(test_build)
+ end
+
+ it 'preloads project on each build to avoid N+1 queries' do
+ create(:ci_build, :test_reports, pipeline: pipeline, project: project)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ pipeline.latest_test_report_builds.map(&:project).map(&:full_path)
+ end
+
+ multi_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project)
+ create(:ci_build, :test_reports, pipeline: multi_build_pipeline, project: project)
+ create(:ci_build, :test_reports, pipeline: multi_build_pipeline, project: project)
+
+ expect { multi_build_pipeline.latest_test_report_builds.map(&:project).map(&:full_path) }
+ .not_to exceed_query_limit(control_count)
+ end
+ end
+
+ describe '#builds_with_failed_tests' do
+ it 'returns pipeline builds with test report artifacts' do
+ failed_build = create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
+ create(:ci_build, :success, :test_reports, pipeline: pipeline, project: project)
+
+ expect(pipeline.builds_with_failed_tests).to contain_exactly(failed_build)
+ end
+
+ it 'supports limiting the number of builds to fetch' do
+ create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
+ create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
+
+ expect(pipeline.builds_with_failed_tests(limit: 1).count).to eq(1)
+ end
+
+ it 'preloads project on each build to avoid N+1 queries' do
+ create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ pipeline.builds_with_failed_tests.map(&:project).map(&:full_path)
+ end
+
+ multi_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project)
+ create(:ci_build, :failed, :test_reports, pipeline: multi_build_pipeline, project: project)
+ create(:ci_build, :failed, :test_reports, pipeline: multi_build_pipeline, project: project)
+
+ expect { multi_build_pipeline.builds_with_failed_tests.map(&:project).map(&:full_path) }
+ .not_to exceed_query_limit(control_count)
+ end
+ end
end
diff --git a/spec/models/clusters/agent_spec.rb b/spec/models/clusters/agent_spec.rb
index 148bb3cf870..49f41570717 100644
--- a/spec/models/clusters/agent_spec.rb
+++ b/spec/models/clusters/agent_spec.rb
@@ -57,4 +57,16 @@ RSpec.describe Clusters::Agent do
end
end
end
+
+ describe '#has_access_to?' do
+ let(:agent) { build(:cluster_agent) }
+
+ it 'has access to own project' do
+ expect(agent.has_access_to?(agent.project)).to be_truthy
+ end
+
+ it 'does not have access to other projects' do
+ expect(agent.has_access_to?(create(:project))).to be_falsey
+ end
+ end
end
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index ad1ebd4966a..5212e321a55 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -19,35 +19,9 @@ RSpec.describe Clusters::Applications::Helm do
end
describe '#can_uninstall?' do
- context "with other existing applications" do
- Clusters::Cluster::APPLICATIONS.keys.each do |application_name|
- next if application_name == 'helm'
+ subject(:application) { build(:clusters_applications_helm).can_uninstall? }
- it "is false when #{application_name} is installed" do
- cluster_application = create("clusters_applications_#{application_name}".to_sym)
-
- helm = cluster_application.cluster.application_helm
-
- expect(helm.allowed_to_uninstall?).to be_falsy
- end
- end
-
- it 'executes a single query only' do
- cluster_application = create(:clusters_applications_ingress)
- helm = cluster_application.cluster.application_helm
-
- query_count = ActiveRecord::QueryRecorder.new { helm.allowed_to_uninstall? }.count
- expect(query_count).to eq(1)
- end
- end
-
- context "without other existing applications" do
- subject { helm.can_uninstall? }
-
- let(:helm) { create(:clusters_applications_helm) }
-
- it { is_expected.to be_truthy }
- end
+ it { is_expected.to eq true }
end
describe '#issue_client_cert' do
@@ -135,14 +109,4 @@ RSpec.describe Clusters::Applications::Helm do
end
end
end
-
- describe '#post_uninstall' do
- let(:helm) { create(:clusters_applications_helm, :installed) }
-
- it do
- expect(helm.cluster.kubeclient).to receive(:delete_namespace).with('gitlab-managed-apps')
-
- helm.post_uninstall
- end
- end
end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index ed74a841044..a8f81cba285 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -262,14 +262,14 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- describe '.with_project_alert_service_data' do
- subject { described_class.with_project_alert_service_data(project_id) }
+ describe '.with_project_http_integrations' do
+ subject { described_class.with_project_http_integrations(project_id) }
let!(:cluster) { create(:cluster, :project) }
let!(:project_id) { cluster.first_project.id }
context 'project has alert service data' do
- let!(:alerts_service) { create(:alerts_service, project: cluster.clusterable) }
+ let!(:integration) { create(:alert_management_http_integration, project: cluster.clusterable) }
it { is_expected.to include(cluster) }
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index e877ba2ac96..fb0613187c5 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Clusters::Platforms::Kubernetes do
include KubernetesHelpers
+ include ReactiveCachingHelpers
it { is_expected.to belong_to(:cluster) }
it { is_expected.to be_kind_of(Gitlab::Kubernetes) }
@@ -406,32 +407,62 @@ RSpec.describe Clusters::Platforms::Kubernetes do
end
describe '#calculate_reactive_cache_for' do
+ let(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
let(:service) { create(:cluster_platform_kubernetes, :configured) }
+ let(:namespace) { 'project-namespace' }
+ let(:environment) { instance_double(Environment, deployment_namespace: namespace, project: cluster.project) }
let(:expected_pod_cached_data) do
kube_pod.tap { |kp| kp['metadata'].delete('namespace') }
end
- let(:namespace) { "project-namespace" }
- let(:environment) { instance_double(Environment, deployment_namespace: namespace, project: service.cluster.project) }
-
subject { service.calculate_reactive_cache_for(environment) }
- context 'when the kubernetes integration is disabled' do
+ context 'when kubernetes responds with valid deployments' do
before do
- allow(service).to receive(:enabled?).and_return(false)
+ stub_kubeclient_pods(namespace)
+ stub_kubeclient_deployments(namespace)
+ stub_kubeclient_ingresses(namespace)
end
- it { is_expected.to be_nil }
+ shared_examples 'successful deployment request' do
+ it { is_expected.to include(pods: [expected_pod_cached_data], deployments: [kube_deployment], ingresses: [kube_ingress]) }
+ end
+
+ context 'on a project level cluster' do
+ let(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
+
+ include_examples 'successful deployment request'
+ end
+
+ context 'on a group level cluster' do
+ let(:cluster) { create(:cluster, :group, platform_kubernetes: service) }
+
+ include_examples 'successful deployment request'
+ end
+
+ context 'on an instance level cluster' do
+ let(:cluster) { create(:cluster, :instance, platform_kubernetes: service) }
+
+ include_examples 'successful deployment request'
+ end
+
+ context 'when canary_ingress_weight_control feature flag is disabled' do
+ before do
+ stub_feature_flags(canary_ingress_weight_control: false)
+ end
+
+ it 'does not fetch ingress data from kubernetes' do
+ expect(subject[:ingresses]).to be_empty
+ end
+ end
end
- context 'when kubernetes responds with valid pods and deployments' do
+ context 'when the kubernetes integration is disabled' do
before do
- stub_kubeclient_pods(namespace)
- stub_kubeclient_deployments(namespace)
- stub_kubeclient_ingresses(namespace)
+ allow(service).to receive(:enabled?).and_return(false)
end
- it { is_expected.to include(pods: [expected_pod_cached_data]) }
+ it { is_expected.to be_nil }
end
context 'when kubernetes responds with 500s' do
@@ -451,7 +482,351 @@ RSpec.describe Clusters::Platforms::Kubernetes do
stub_kubeclient_ingresses(namespace, status: 404)
end
- it { is_expected.to include(pods: []) }
+ it { is_expected.to eq(pods: [], deployments: [], ingresses: []) }
+ end
+ end
+
+ describe '#rollout_status' do
+ let(:deployments) { [] }
+ let(:pods) { [] }
+ let(:ingresses) { [] }
+ let(:service) { create(:cluster_platform_kubernetes, :configured) }
+ let!(:cluster) { create(:cluster, :project, enabled: true, platform_kubernetes: service) }
+ let(:project) { cluster.project }
+ let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
+ let(:cache_data) { Hash(deployments: deployments, pods: pods, ingresses: ingresses) }
+
+ subject(:rollout_status) { service.rollout_status(environment, cache_data) }
+
+ context 'legacy deployments based on app label' do
+ let(:legacy_deployment) do
+ kube_deployment(name: 'legacy-deployment').tap do |deployment|
+ deployment['metadata']['annotations'].delete('app.gitlab.com/env')
+ deployment['metadata']['annotations'].delete('app.gitlab.com/app')
+ deployment['metadata']['labels']['app'] = environment.slug
+ end
+ end
+
+ let(:legacy_pod) do
+ kube_pod(name: 'legacy-pod').tap do |pod|
+ pod['metadata']['annotations'].delete('app.gitlab.com/env')
+ pod['metadata']['annotations'].delete('app.gitlab.com/app')
+ pod['metadata']['labels']['app'] = environment.slug
+ end
+ end
+
+ context 'only legacy deployments' do
+ let(:deployments) { [legacy_deployment] }
+ let(:pods) { [legacy_pod] }
+
+ it 'contains nothing' do
+ expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
+
+ expect(rollout_status.deployments).to eq([])
+ end
+ end
+
+ context 'deployment with no pods' do
+ let(:deployment) { kube_deployment(name: 'some-deployment', environment_slug: environment.slug, project_slug: project.full_path_slug) }
+ let(:deployments) { [deployment] }
+ let(:pods) { [] }
+
+ it 'returns a valid status with matching deployments' do
+ expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
+ expect(rollout_status.deployments.map(&:name)).to contain_exactly('some-deployment')
+ end
+ end
+
+ context 'new deployment based on annotations' do
+ let(:matched_deployment) { kube_deployment(name: 'matched-deployment', environment_slug: environment.slug, project_slug: project.full_path_slug) }
+ let(:matched_pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) }
+ let(:deployments) { [matched_deployment, legacy_deployment] }
+ let(:pods) { [matched_pod, legacy_pod] }
+
+ it 'contains only matching deployments' do
+ expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
+
+ expect(rollout_status.deployments.map(&:name)).to contain_exactly('matched-deployment')
+ end
+ end
+ end
+
+ context 'with no deployments but there are pods' do
+ let(:deployments) do
+ []
+ end
+
+ let(:pods) do
+ [
+ kube_pod(name: 'pod-1', environment_slug: environment.slug, project_slug: project.full_path_slug),
+ kube_pod(name: 'pod-2', environment_slug: environment.slug, project_slug: project.full_path_slug)
+ ]
+ end
+
+ it 'returns an empty array' do
+ expect(rollout_status.instances).to eq([])
+ end
+ end
+
+ context 'with valid deployments' do
+ let(:matched_deployment) { kube_deployment(environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2) }
+ let(:unmatched_deployment) { kube_deployment }
+ let(:matched_pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: 'Pending') }
+ let(:unmatched_pod) { kube_pod(environment_slug: environment.slug + '-test', project_slug: project.full_path_slug) }
+ let(:deployments) { [matched_deployment, unmatched_deployment] }
+ let(:pods) { [matched_pod, unmatched_pod] }
+
+ it 'creates a matching RolloutStatus' do
+ expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
+ expect(rollout_status.deployments.map(&:annotations)).to eq([
+ { 'app.gitlab.com/app' => project.full_path_slug, 'app.gitlab.com/env' => 'env-000000' }
+ ])
+ expect(rollout_status.instances).to eq([{ pod_name: "kube-pod",
+ stable: true,
+ status: "pending",
+ tooltip: "kube-pod (Pending)",
+ track: "stable" },
+ { pod_name: "Not provided",
+ stable: true,
+ status: "pending",
+ tooltip: "Not provided (Pending)",
+ track: "stable" }])
+ end
+
+ context 'with canary ingress' do
+ let(:ingresses) { [kube_ingress(track: :canary)] }
+
+ it 'has canary ingress' do
+ expect(rollout_status).to be_canary_ingress_exists
+ expect(rollout_status.canary_ingress.canary_weight).to eq(50)
+ end
+ end
+ end
+
+ context 'with empty list of deployments' do
+ it 'creates a matching RolloutStatus' do
+ expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
+ expect(rollout_status).to be_not_found
+ end
+ end
+
+ context 'when the pod track does not match the deployment track' do
+ let(:deployments) do
+ [
+ kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'weekly')
+ ]
+ end
+
+ let(:pods) do
+ [
+ kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'weekly'),
+ kube_pod(name: 'pod-a-2', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'daily')
+ ]
+ end
+
+ it 'does not return the pod' do
+ expect(rollout_status.instances.map { |p| p[:pod_name] }).to eq(['pod-a-1'])
+ end
+ end
+
+ context 'when the pod track is not stable' do
+ let(:deployments) do
+ [
+ kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'something')
+ ]
+ end
+
+ let(:pods) do
+ [
+ kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'something')
+ ]
+ end
+
+ it 'the pod is not stable' do
+ expect(rollout_status.instances.map { |p| p.slice(:stable, :track) }).to eq([{ stable: false, track: 'something' }])
+ end
+ end
+
+ context 'when the pod track is stable' do
+ let(:deployments) do
+ [
+ kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'stable')
+ ]
+ end
+
+ let(:pods) do
+ [
+ kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'stable')
+ ]
+ end
+
+ it 'the pod is stable' do
+ expect(rollout_status.instances.map { |p| p.slice(:stable, :track) }).to eq([{ stable: true, track: 'stable' }])
+ end
+ end
+
+ context 'when the pod track is not provided' do
+ let(:deployments) do
+ [
+ kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1)
+ ]
+ end
+
+ let(:pods) do
+ [
+ kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug)
+ ]
+ end
+
+ it 'the pod is stable' do
+ expect(rollout_status.instances.map { |p| p.slice(:stable, :track) }).to eq([{ stable: true, track: 'stable' }])
+ end
+ end
+
+ context 'when the number of matching pods does not match the number of replicas' do
+ let(:deployments) do
+ [
+ kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 3)
+ ]
+ end
+
+ let(:pods) do
+ [
+ kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug)
+ ]
+ end
+
+ it 'returns a pending pod for each missing replica' do
+ expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status) }).to eq([
+ { pod_name: 'pod-a-1', status: 'running' },
+ { pod_name: 'Not provided', status: 'pending' },
+ { pod_name: 'Not provided', status: 'pending' }
+ ])
+ end
+ end
+
+ context 'when pending pods are returned for missing replicas' do
+ let(:deployments) do
+ [
+ kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2, track: 'canary'),
+ kube_deployment(name: 'deployment-b', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2, track: 'stable')
+ ]
+ end
+
+ let(:pods) do
+ [
+ kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'canary')
+ ]
+ end
+
+ it 'returns the correct track for the pending pods' do
+ expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status, :track) }).to eq([
+ { pod_name: 'pod-a-1', status: 'running', track: 'canary' },
+ { pod_name: 'Not provided', status: 'pending', track: 'canary' },
+ { pod_name: 'Not provided', status: 'pending', track: 'stable' },
+ { pod_name: 'Not provided', status: 'pending', track: 'stable' }
+ ])
+ end
+ end
+
+ context 'when two deployments with the same track are missing instances' do
+ let(:deployments) do
+ [
+ kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'mytrack'),
+ kube_deployment(name: 'deployment-b', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'mytrack')
+ ]
+ end
+
+ let(:pods) do
+ []
+ end
+
+ it 'returns the correct number of pending pods' do
+ expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status, :track) }).to eq([
+ { pod_name: 'Not provided', status: 'pending', track: 'mytrack' },
+ { pod_name: 'Not provided', status: 'pending', track: 'mytrack' }
+ ])
+ end
+ end
+
+ context 'with multiple matching deployments' do
+ let(:deployments) do
+ [
+ kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2),
+ kube_deployment(name: 'deployment-b', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2)
+ ]
+ end
+
+ let(:pods) do
+ [
+ kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug),
+ kube_pod(name: 'pod-a-2', environment_slug: environment.slug, project_slug: project.full_path_slug),
+ kube_pod(name: 'pod-b-1', environment_slug: environment.slug, project_slug: project.full_path_slug),
+ kube_pod(name: 'pod-b-2', environment_slug: environment.slug, project_slug: project.full_path_slug)
+ ]
+ end
+
+ it 'returns each pod once' do
+ expect(rollout_status.instances.map { |p| p[:pod_name] }).to eq(['pod-a-1', 'pod-a-2', 'pod-b-1', 'pod-b-2'])
+ end
+ end
+ end
+
+ describe '#ingresses' do
+ subject { service.ingresses(namespace) }
+
+ let(:service) { create(:cluster_platform_kubernetes, :configured) }
+ let(:namespace) { 'project-namespace' }
+
+ context 'when there is an ingress in the namespace' do
+ before do
+ stub_kubeclient_ingresses(namespace)
+ end
+
+ it 'returns an ingress' do
+ expect(subject.count).to eq(1)
+ expect(subject.first).to be_kind_of(::Gitlab::Kubernetes::Ingress)
+ expect(subject.first.name).to eq('production-auto-deploy')
+ end
+ end
+
+ context 'when there are no ingresss in the namespace' do
+ before do
+ allow(service.kubeclient).to receive(:get_ingresses) { raise Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil) }
+ end
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '#patch_ingress' do
+ subject { service.patch_ingress(namespace, ingress, data) }
+
+ let(:service) { create(:cluster_platform_kubernetes, :configured) }
+ let(:namespace) { 'project-namespace' }
+ let(:ingress) { Gitlab::Kubernetes::Ingress.new(kube_ingress) }
+ let(:data) { { metadata: { annotations: { name: 'test' } } } }
+
+ context 'when there is an ingress in the namespace' do
+ before do
+ stub_kubeclient_ingresses(namespace, method: :patch, resource_path: "/#{ingress.name}")
+ end
+
+ it 'returns an ingress' do
+ expect(subject[:items][0][:metadata][:name]).to eq('production-auto-deploy')
+ end
+ end
+
+ context 'when there are no ingresss in the namespace' do
+ before do
+ allow(service.kubeclient).to receive(:patch_ingress) { raise Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil) }
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Kubeclient::ResourceNotFoundError)
+ end
end
end
end
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 37e2f5fb8d4..6e62d4ef31b 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -233,7 +233,7 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do
end
it 'calls #refresh_markdown_cache!' do
- expect(thing).to receive(:refresh_markdown_cache!)
+ expect(thing).to receive(:refresh_markdown_cache)
expect(thing.updated_cached_html_for(:description)).to eq(html)
end
@@ -279,10 +279,101 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do
end
end
+ shared_examples 'a class with mentionable markdown fields' do
+ let(:mentionable) { klass.new(description: markdown, description_html: html, title: markdown, title_html: html, cached_markdown_version: cache_version) }
+
+ context 'when klass is a Mentionable', :aggregate_failures do
+ before do
+ klass.send(:include, Mentionable)
+ klass.send(:attr_mentionable, :description)
+ end
+
+ describe '#mentionable_attributes_changed?' do
+ message = Struct.new(:text)
+
+ let(:changes) do
+ msg = message.new('test')
+
+ changes = {}
+ changes[msg] = ['', 'some message']
+ changes[:random_sym_key] = ['', 'some message']
+ changes["description"] = ['', 'some message']
+ changes
+ end
+
+ it 'returns true with key string' do
+ changes["description_html"] = ['', 'some message']
+
+ allow(mentionable).to receive(:saved_changes).and_return(changes)
+
+ expect(mentionable.send(:mentionable_attributes_changed?)).to be true
+ end
+
+ it 'returns false with key symbol' do
+ changes[:description_html] = ['', 'some message']
+ allow(mentionable).to receive(:saved_changes).and_return(changes)
+
+ expect(mentionable.send(:mentionable_attributes_changed?)).to be false
+ end
+
+ it 'returns false when no attr_mentionable keys' do
+ allow(mentionable).to receive(:saved_changes).and_return(changes)
+
+ expect(mentionable.send(:mentionable_attributes_changed?)).to be false
+ end
+ end
+
+ describe '#save' do
+ context 'when cache is outdated' do
+ before do
+ thing.cached_markdown_version += 1
+ end
+
+ context 'when the markdown field also a mentionable attribute' do
+ let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) }
+
+ it 'calls #store_mentions!' do
+ expect(thing).to receive(:mentionable_attributes_changed?).and_return(true)
+ expect(thing).to receive(:store_mentions!)
+
+ thing.try(:save)
+
+ expect(thing.description_html).to eq(html)
+ end
+ end
+
+ context 'when the markdown field is not mentionable attribute' do
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+
+ it 'does not call #store_mentions!' do
+ expect(thing).not_to receive(:store_mentions!)
+ expect(thing).to receive(:refresh_markdown_cache)
+
+ thing.try(:save)
+
+ expect(thing.title_html).to eq(html)
+ end
+ end
+ end
+
+ context 'when the markdown field does not exist' do
+ let(:thing) { klass.new(cached_markdown_version: cache_version) }
+
+ it 'does not call #store_mentions!' do
+ expect(thing).not_to receive(:store_mentions!)
+
+ thing.try(:save)
+ end
+ end
+ end
+ end
+ end
+
context 'for Active record classes' do
let(:klass) { ar_class }
it_behaves_like 'a class with cached markdown fields'
+ it_behaves_like 'a class with mentionable markdown fields'
describe '#attribute_invalidated?' do
let(:thing) { klass.create!(description: markdown, description_html: html, cached_markdown_version: cache_version) }
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index 5fb7cdb4443..7cf7b825d7d 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -4,16 +4,16 @@ require 'spec_helper'
RSpec.describe CaseSensitivity do
describe '.iwhere' do
- let(:connection) { ActiveRecord::Base.connection }
- let(:model) do
+ let_it_be(:connection) { ActiveRecord::Base.connection }
+ let_it_be(:model) do
Class.new(ActiveRecord::Base) do
include CaseSensitivity
self.table_name = 'namespaces'
end
end
- let!(:model_1) { model.create!(path: 'mOdEl-1', name: 'mOdEl 1') }
- let!(:model_2) { model.create!(path: 'mOdEl-2', name: 'mOdEl 2') }
+ let_it_be(:model_1) { model.create!(path: 'mOdEl-1', name: 'mOdEl 1') }
+ let_it_be(:model_2) { model.create!(path: 'mOdEl-2', name: 'mOdEl 2') }
it 'finds a single instance by a single attribute regardless of case' do
expect(model.iwhere(path: 'MODEL-1')).to contain_exactly(model_1)
@@ -28,6 +28,10 @@ RSpec.describe CaseSensitivity do
.to contain_exactly(model_1)
end
+ it 'finds instances by custom Arel attributes' do
+ expect(model.iwhere(model.arel_table[:path] => 'MODEL-1')).to contain_exactly(model_1)
+ end
+
it 'builds a query using LOWER' do
query = model.iwhere(path: %w(MODEL-1 model-2), name: 'model 1').to_sql
expected_query = <<~QRY.strip
diff --git a/spec/models/concerns/ignorable_columns_spec.rb b/spec/models/concerns/ignorable_columns_spec.rb
index a5eff154a0b..c97dc606159 100644
--- a/spec/models/concerns/ignorable_columns_spec.rb
+++ b/spec/models/concerns/ignorable_columns_spec.rb
@@ -59,6 +59,14 @@ RSpec.describe IgnorableColumns do
it_behaves_like 'storing removal information'
end
+ context 'when called on a subclass without setting the ignored columns' do
+ let(:subclass) { Class.new(record_class) }
+
+ it 'does not raise Deadlock error' do
+ expect { subclass.ignored_columns_details }.not_to raise_error
+ end
+ end
+
it 'defaults to empty Hash' do
expect(subject.ignored_columns_details).to eq({})
end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 516c0fd75bc..3c095477ea9 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -29,42 +29,6 @@ RSpec.describe Mentionable do
expect(mentionable.referenced_mentionables).to be_empty
end
end
-
- describe '#any_mentionable_attributes_changed?' do
- message = Struct.new(:text)
-
- let(:mentionable) { Example.new }
- let(:changes) do
- msg = message.new('test')
-
- changes = {}
- changes[msg] = ['', 'some message']
- changes[:random_sym_key] = ['', 'some message']
- changes["random_string_key"] = ['', 'some message']
- changes
- end
-
- it 'returns true with key string' do
- changes["message"] = ['', 'some message']
-
- allow(mentionable).to receive(:saved_changes).and_return(changes)
-
- expect(mentionable.send(:any_mentionable_attributes_changed?)).to be true
- end
-
- it 'returns false with key symbol' do
- changes[:message] = ['', 'some message']
- allow(mentionable).to receive(:saved_changes).and_return(changes)
-
- expect(mentionable.send(:any_mentionable_attributes_changed?)).to be false
- end
-
- it 'returns false when no attr_mentionable keys' do
- allow(mentionable).to receive(:saved_changes).and_return(changes)
-
- expect(mentionable.send(:any_mentionable_attributes_changed?)).to be false
- end
- end
end
RSpec.describe Issue, "Mentionable" do
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
index ba70ff563a8..2059e170446 100644
--- a/spec/models/concerns/project_features_compatibility_spec.rb
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe ProjectFeaturesCompatibility do
let(:project) { create(:project) }
let(:features_enabled) { %w(issues wiki builds merge_requests snippets) }
- let(:features) { features_enabled + %w(repository pages) }
+ let(:features) { features_enabled + %w(repository pages operations) }
# We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table
# All those fields got moved to a new table called project_feature and are now integers instead of booleans
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index e4cf68663ef..6ab87053258 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -2,8 +2,69 @@
require 'spec_helper'
+RSpec.shared_examples '.find_by_full_path' do
+ describe '.find_by_full_path', :aggregate_failures do
+ it 'finds records by their full path' do
+ expect(described_class.find_by_full_path(record.full_path)).to eq(record)
+ expect(described_class.find_by_full_path(record.full_path.upcase)).to eq(record)
+ end
+
+ it 'returns nil for unknown paths' do
+ expect(described_class.find_by_full_path('unknown')).to be_nil
+ end
+
+ it 'includes route information when loading a record' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ described_class.find_by_full_path(record.full_path)
+ end.count
+
+ expect do
+ described_class.find_by_full_path(record.full_path).route
+ end.not_to exceed_all_query_limit(control_count)
+ end
+
+ context 'with redirect routes' do
+ let_it_be(:redirect_route) { create(:redirect_route, source: record) }
+
+ context 'without follow_redirects option' do
+ it 'does not find records by their redirected path' do
+ expect(described_class.find_by_full_path(redirect_route.path)).to be_nil
+ expect(described_class.find_by_full_path(redirect_route.path.upcase)).to be_nil
+ end
+ end
+
+ context 'with follow_redirects option set to true' do
+ it 'finds records by their canonical path' do
+ expect(described_class.find_by_full_path(record.full_path, follow_redirects: true)).to eq(record)
+ expect(described_class.find_by_full_path(record.full_path.upcase, follow_redirects: true)).to eq(record)
+ end
+
+ it 'finds records by their redirected path' do
+ expect(described_class.find_by_full_path(redirect_route.path, follow_redirects: true)).to eq(record)
+ expect(described_class.find_by_full_path(redirect_route.path.upcase, follow_redirects: true)).to eq(record)
+ end
+
+ it 'returns nil for unknown paths' do
+ expect(described_class.find_by_full_path('unknown', follow_redirects: true)).to be_nil
+ end
+ end
+ end
+ end
+end
+
+RSpec.describe Routable do
+ it_behaves_like '.find_by_full_path' do
+ let_it_be(:record) { create(:group) }
+ end
+
+ it_behaves_like '.find_by_full_path' do
+ let_it_be(:record) { create(:project) }
+ end
+end
+
RSpec.describe Group, 'Routable' do
- let!(:group) { create(:group, name: 'foo') }
+ let_it_be_with_reload(:group) { create(:group, name: 'foo') }
+ let_it_be(:nested_group) { create(:group, parent: group) }
describe 'Validations' do
it { is_expected.to validate_presence_of(:route) }
@@ -59,61 +120,20 @@ RSpec.describe Group, 'Routable' do
end
describe '.find_by_full_path' do
- let!(:nested_group) { create(:group, parent: group) }
-
- context 'without any redirect routes' do
- it { expect(described_class.find_by_full_path(group.to_param)).to eq(group) }
- it { expect(described_class.find_by_full_path(group.to_param.upcase)).to eq(group) }
- it { expect(described_class.find_by_full_path(nested_group.to_param)).to eq(nested_group) }
- it { expect(described_class.find_by_full_path('unknown')).to eq(nil) }
-
- it 'includes route information when loading a record' do
- path = group.to_param
- control_count = ActiveRecord::QueryRecorder.new { described_class.find_by_full_path(path) }.count
-
- expect { described_class.find_by_full_path(path).route }.not_to exceed_all_query_limit(control_count)
- end
+ it_behaves_like '.find_by_full_path' do
+ let_it_be(:record) { group }
end
- context 'with redirect routes' do
- let!(:group_redirect_route) { group.redirect_routes.create!(path: 'bar') }
- let!(:nested_group_redirect_route) { nested_group.redirect_routes.create!(path: nested_group.path.sub('foo', 'bar')) }
-
- context 'without follow_redirects option' do
- context 'with the given path not matching any route' do
- it { expect(described_class.find_by_full_path('unknown')).to eq(nil) }
- end
-
- context 'with the given path matching the canonical route' do
- it { expect(described_class.find_by_full_path(group.to_param)).to eq(group) }
- it { expect(described_class.find_by_full_path(group.to_param.upcase)).to eq(group) }
- it { expect(described_class.find_by_full_path(nested_group.to_param)).to eq(nested_group) }
- end
-
- context 'with the given path matching a redirect route' do
- it { expect(described_class.find_by_full_path(group_redirect_route.path)).to eq(nil) }
- it { expect(described_class.find_by_full_path(group_redirect_route.path.upcase)).to eq(nil) }
- it { expect(described_class.find_by_full_path(nested_group_redirect_route.path)).to eq(nil) }
- end
- end
-
- context 'with follow_redirects option set to true' do
- context 'with the given path not matching any route' do
- it { expect(described_class.find_by_full_path('unknown', follow_redirects: true)).to eq(nil) }
- end
+ it_behaves_like '.find_by_full_path' do
+ let_it_be(:record) { nested_group }
+ end
- context 'with the given path matching the canonical route' do
- it { expect(described_class.find_by_full_path(group.to_param, follow_redirects: true)).to eq(group) }
- it { expect(described_class.find_by_full_path(group.to_param.upcase, follow_redirects: true)).to eq(group) }
- it { expect(described_class.find_by_full_path(nested_group.to_param, follow_redirects: true)).to eq(nested_group) }
- end
+ it 'does not find projects with a matching path' do
+ project = create(:project)
+ redirect_route = create(:redirect_route, source: project)
- context 'with the given path matching a redirect route' do
- it { expect(described_class.find_by_full_path(group_redirect_route.path, follow_redirects: true)).to eq(group) }
- it { expect(described_class.find_by_full_path(group_redirect_route.path.upcase, follow_redirects: true)).to eq(group) }
- it { expect(described_class.find_by_full_path(nested_group_redirect_route.path, follow_redirects: true)).to eq(nested_group) }
- end
- end
+ expect(described_class.find_by_full_path(project.full_path)).to be_nil
+ expect(described_class.find_by_full_path(redirect_route.path, follow_redirects: true)).to be_nil
end
end
@@ -131,8 +151,6 @@ RSpec.describe Group, 'Routable' do
end
context 'with valid paths' do
- let!(:nested_group) { create(:group, parent: group) }
-
it 'returns the projects matching the paths' do
result = described_class.where_full_path_in([group.to_param, nested_group.to_param])
@@ -148,32 +166,36 @@ RSpec.describe Group, 'Routable' do
end
describe '#full_path' do
- let(:group) { create(:group) }
- let(:nested_group) { create(:group, parent: group) }
-
it { expect(group.full_path).to eq(group.path) }
it { expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}") }
end
describe '#full_name' do
- let(:group) { create(:group) }
- let(:nested_group) { create(:group, parent: group) }
-
it { expect(group.full_name).to eq(group.name) }
it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
end
end
RSpec.describe Project, 'Routable' do
- describe '#full_path' do
- let(:project) { build_stubbed(:project) }
+ let_it_be(:project) { create(:project) }
+ it_behaves_like '.find_by_full_path' do
+ let_it_be(:record) { project }
+ end
+
+ it 'does not find groups with a matching path' do
+ group = create(:group)
+ redirect_route = create(:redirect_route, source: group)
+
+ expect(described_class.find_by_full_path(group.full_path)).to be_nil
+ expect(described_class.find_by_full_path(redirect_route.path, follow_redirects: true)).to be_nil
+ end
+
+ describe '#full_path' do
it { expect(project.full_path).to eq "#{project.namespace.full_path}/#{project.path}" }
end
describe '#full_name' do
- let(:project) { build_stubbed(:project) }
-
it { expect(project.full_name).to eq "#{project.namespace.human_name} / #{project.name}" }
end
end
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 90e94b5dca9..d8b77e1cd0d 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -105,8 +105,8 @@ RSpec.describe PersonalAccessToken, 'TokenAuthenticatable' do
it 'sets new token' do
subject
- expect(personal_access_token.token).to eq(token_value)
- expect(personal_access_token.token_digest).to eq(Gitlab::CryptoHelper.sha256(token_value))
+ expect(personal_access_token.token).to eq("#{PersonalAccessToken.token_prefix}#{token_value}")
+ expect(personal_access_token.token_digest).to eq(Gitlab::CryptoHelper.sha256("#{PersonalAccessToken.token_prefix}#{token_value}"))
end
end
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 2adceb1c960..0ecefff3a97 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -188,7 +188,7 @@ RSpec.describe ContainerRepository do
subject { repository.start_expiration_policy! }
it 'sets the expiration policy started at to now' do
- Timecop.freeze do
+ freeze_time do
expect { subject }
.to change { repository.expiration_policy_started_at }.from(nil).to(Time.zone.now)
end
diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb
index 62380299ea0..41ce480b02f 100644
--- a/spec/models/custom_emoji_spec.rb
+++ b/spec/models/custom_emoji_spec.rb
@@ -22,6 +22,15 @@ RSpec.describe CustomEmoji do
expect(new_emoji.errors.messages).to eq(name: ["#{emoji_name} is already being used for another emoji"])
end
+ it 'disallows very long invalid emoji name without regular expression backtracking issues' do
+ new_emoji = build(:custom_emoji, name: 'a' * 10000 + '!', group: group)
+
+ Timeout.timeout(1) do
+ expect(new_emoji).not_to be_valid
+ expect(new_emoji.errors.messages).to eq(name: ["is too long (maximum is 36 characters)", "is invalid"])
+ end
+ end
+
it 'disallows duplicate custom emoji names within namespace' do
old_emoji = create(:custom_emoji, group: group)
new_emoji = build(:custom_emoji, name: old_emoji.name, namespace: old_emoji.namespace, group: group)
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index 8900c49a662..ca612cba654 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'CycleAnalytics#code' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { project.owner }
- let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
subject { project_level }
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 5857365ceab..66d21f6925f 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'CycleAnalytics#issue' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { project.owner }
- let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
subject { project_level }
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index 2b9be64da2b..acaf767db01 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'CycleAnalytics#plan' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { project.owner }
- let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
subject { project_level }
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 6ebbcebd71d..06d9cfbf8c0 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe 'CycleAnalytics#review' do
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { project.owner }
- subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
generate_cycle_analytics_spec(
phase: :review,
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 024625d229f..50cb49d6309 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'CycleAnalytics#staging' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { project.owner }
- let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
subject { project_level }
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index 7010d69f8a4..8f65c047b15 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe 'CycleAnalytics#test' do
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { project.owner }
let_it_be(:issue) { create(:issue, project: project) }
- let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
let!(:merge_request) { create_merge_request_closing_issue(user, project, issue) }
subject { project_level }
diff --git a/spec/models/dependency_proxy/manifest_spec.rb b/spec/models/dependency_proxy/manifest_spec.rb
new file mode 100644
index 00000000000..aa2e73356dd
--- /dev/null
+++ b/spec/models/dependency_proxy/manifest_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe DependencyProxy::Manifest, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:group) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:group) }
+ it { is_expected.to validate_presence_of(:file) }
+ it { is_expected.to validate_presence_of(:file_name) }
+ it { is_expected.to validate_presence_of(:digest) }
+ end
+
+ describe 'file is being stored' do
+ subject { create(:dependency_proxy_manifest) }
+
+ context 'when existing object has local store' do
+ it_behaves_like 'mounted file in local store'
+ end
+
+ context 'when direct upload is enabled' do
+ before do
+ stub_dependency_proxy_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'mounted file in object store'
+ end
+ end
+
+ describe '.find_or_initialize_by_file_name' do
+ subject { DependencyProxy::Manifest.find_or_initialize_by_file_name(file_name) }
+
+ context 'no manifest exists' do
+ let_it_be(:file_name) { 'foo' }
+
+ it 'initializes a manifest' do
+ expect(DependencyProxy::Manifest).to receive(:new).with(file_name: file_name)
+
+ subject
+ end
+ end
+
+ context 'manifest exists' do
+ let_it_be(:dependency_proxy_manifest) { create(:dependency_proxy_manifest) }
+ let_it_be(:file_name) { dependency_proxy_manifest.file_name }
+
+ it { is_expected.to eq(dependency_proxy_manifest) }
+ end
+ end
+end
diff --git a/spec/models/dependency_proxy/registry_spec.rb b/spec/models/dependency_proxy/registry_spec.rb
index 5bfa75a2eed..a888ee2b7f7 100644
--- a/spec/models/dependency_proxy/registry_spec.rb
+++ b/spec/models/dependency_proxy/registry_spec.rb
@@ -54,4 +54,11 @@ RSpec.describe DependencyProxy::Registry, type: :model do
end
end
end
+
+ describe '#authenticate_header' do
+ it 'returns the OAuth realm and service header' do
+ expect(described_class.authenticate_header)
+ .to eq("Bearer realm=\"#{Gitlab.config.gitlab.url}/jwt/auth\",service=\"dependency_proxy\"")
+ end
+ end
end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 9afacd518af..c962b012a4b 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -202,6 +202,31 @@ RSpec.describe Deployment do
deployment.cancel!
end
end
+
+ context 'when deployment was skipped' do
+ let(:deployment) { create(:deployment, :running) }
+
+ it 'has correct status' do
+ deployment.skip!
+
+ expect(deployment).to be_skipped
+ expect(deployment.finished_at).to be_nil
+ end
+
+ it 'does not execute Deployments::LinkMergeRequestWorker asynchronously' do
+ expect(Deployments::LinkMergeRequestWorker)
+ .not_to receive(:perform_async).with(deployment.id)
+
+ deployment.skip!
+ end
+
+ it 'does not execute Deployments::ExecuteHooksWorker' do
+ expect(Deployments::ExecuteHooksWorker)
+ .not_to receive(:perform_async).with(deployment.id)
+
+ deployment.skip!
+ end
+ end
end
describe '#success?' do
@@ -320,6 +345,7 @@ RSpec.describe Deployment do
deployment2 = create(:deployment, status: :running )
create(:deployment, status: :failed )
create(:deployment, status: :canceled )
+ create(:deployment, status: :skipped)
is_expected.to contain_exactly(deployment1, deployment2)
end
@@ -346,10 +372,70 @@ RSpec.describe Deployment do
it 'retrieves deployments with deployable builds' do
with_deployable = create(:deployment)
create(:deployment, deployable: nil)
+ create(:deployment, deployable_type: 'CommitStatus', deployable_id: non_existing_record_id)
is_expected.to contain_exactly(with_deployable)
end
end
+
+ describe 'finished_between' do
+ subject { described_class.finished_between(start_time, end_time) }
+
+ let_it_be(:start_time) { DateTime.new(2017) }
+ let_it_be(:end_time) { DateTime.new(2019) }
+ let_it_be(:deployment_2016) { create(:deployment, finished_at: DateTime.new(2016)) }
+ let_it_be(:deployment_2017) { create(:deployment, finished_at: DateTime.new(2017)) }
+ let_it_be(:deployment_2018) { create(:deployment, finished_at: DateTime.new(2018)) }
+ let_it_be(:deployment_2019) { create(:deployment, finished_at: DateTime.new(2019)) }
+ let_it_be(:deployment_2020) { create(:deployment, finished_at: DateTime.new(2020)) }
+
+ it 'retrieves deployments that finished between the specified times' do
+ is_expected.to contain_exactly(deployment_2017, deployment_2018)
+ end
+ end
+
+ describe 'visible' do
+ subject { described_class.visible }
+
+ it 'retrieves the visible deployments' do
+ deployment1 = create(:deployment, status: :running)
+ deployment2 = create(:deployment, status: :success)
+ deployment3 = create(:deployment, status: :failed)
+ deployment4 = create(:deployment, status: :canceled)
+ create(:deployment, status: :skipped)
+
+ is_expected.to contain_exactly(deployment1, deployment2, deployment3, deployment4)
+ end
+ end
+ end
+
+ describe 'latest_for_sha' do
+ subject { described_class.latest_for_sha(sha) }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:commits) { project.repository.commits('master', limit: 2) }
+ let_it_be(:deployments) { commits.reverse.map { |commit| create(:deployment, project: project, sha: commit.id) } }
+ let(:sha) { commits.map(&:id) }
+
+ it 'finds the latest deployment with sha' do
+ is_expected.to eq(deployments.last)
+ end
+
+ context 'when sha is old' do
+ let(:sha) { commits.last.id }
+
+ it 'finds the latest deployment with sha' do
+ is_expected.to eq(deployments.first)
+ end
+ end
+
+ context 'when sha is nil' do
+ let(:sha) { nil }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
end
describe '#includes_commit?' do
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index f7ce44f7281..215c733f26b 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -38,6 +38,26 @@ RSpec.describe DiffNote do
it_behaves_like 'a valid diff positionable note' do
subject { build(:diff_note_on_commit, project: project, commit_id: commit_id, position: position) }
end
+
+ it "is not valid when noteable is empty" do
+ note = build(:diff_note_on_merge_request, project: project, noteable: nil)
+
+ note.valid?
+
+ expect(note.errors[:noteable]).to include("doesn't support new-style diff notes")
+ end
+
+ context 'when importing' do
+ it "does not check if it's supported" do
+ note = build(:diff_note_on_merge_request, project: project, noteable: nil)
+ note.importing = true
+ note.valid?
+
+ expect(note.errors.full_messages).not_to include(
+ "Noteable doesn't support new-style diff notes"
+ )
+ end
+ end
end
describe "#position=" do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 179f2a1b0e0..3e10ea847ba 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -19,6 +19,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
it { is_expected.to have_many(:deployments) }
it { is_expected.to have_many(:metrics_dashboard_annotations) }
it { is_expected.to have_many(:alert_management_alerts) }
+ it { is_expected.to have_one(:upcoming_deployment) }
it { is_expected.to have_one(:latest_opened_most_severe_alert) }
it { is_expected.to delegate_method(:stop_action).to(:last_deployment) }
@@ -723,6 +724,22 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
end
end
+ describe '#upcoming_deployment' do
+ subject { environment.upcoming_deployment }
+
+ context 'when environment has a successful deployment' do
+ let!(:deployment) { create(:deployment, :success, environment: environment, project: project) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when environment has a running deployment' do
+ let!(:deployment) { create(:deployment, :running, environment: environment, project: project) }
+
+ it { is_expected.to eq(deployment) }
+ end
+ end
+
describe '#has_terminals?' do
subject { environment.has_terminals? }
@@ -860,16 +877,6 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
expect(described_class.reactive_cache_hard_limit).to eq(10.megabyte)
end
- it 'overrides reactive_cache_limit_enabled? with a FF' do
- environment_with_enabled_ff = build(:environment, project: create(:project))
- environment_with_disabled_ff = build(:environment, project: create(:project))
-
- stub_feature_flags(reactive_caching_limit_environment: environment_with_enabled_ff.project)
-
- expect(environment_with_enabled_ff.send(:reactive_cache_limit_enabled?)).to be_truthy
- expect(environment_with_disabled_ff.send(:reactive_cache_limit_enabled?)).to be_falsey
- end
-
it 'returns cache data from the deployment platform' do
expect(environment.deployment_platform).to receive(:calculate_reactive_cache_for)
.with(environment).and_return(pods: %w(pod1 pod2))
@@ -1394,4 +1401,150 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
it { is_expected.to be(false) }
end
end
+
+ describe '#cancel_deployment_jobs!' do
+ subject { environment.cancel_deployment_jobs! }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:environment, reload: true) { create(:environment, project: project) }
+ let!(:deployment) { create(:deployment, project: project, environment: environment, deployable: build) }
+ let!(:build) { create(:ci_build, :running, project: project, environment: environment) }
+
+ it 'cancels an active deployment job' do
+ subject
+
+ expect(build.reset).to be_canceled
+ end
+
+ context 'when deployable does not exist' do
+ before do
+ deployment.update_column(:deployable_id, non_existing_record_id)
+ end
+
+ it 'does not raise an error' do
+ expect { subject }.not_to raise_error
+
+ expect(build.reset).to be_running
+ end
+ end
+ end
+
+ describe '#rollout_status' do
+ let!(:cluster) { create(:cluster, :project, :provided_by_user, projects: [project]) }
+ let!(:environment) { create(:environment, project: project) }
+ let!(:deployment) { create(:deployment, :success, environment: environment, project: project) }
+
+ subject { environment.rollout_status }
+
+ context 'environment does not have a deployment board available' do
+ before do
+ allow(environment).to receive(:has_terminals?).and_return(false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'cached rollout status is present' do
+ let(:pods) { %w(pod1 pod2) }
+ let(:deployments) { %w(deployment1 deployment2) }
+
+ before do
+ stub_reactive_cache(environment, pods: pods, deployments: deployments)
+ end
+
+ it 'fetches the rollout status from the deployment platform' do
+ expect(environment.deployment_platform).to receive(:rollout_status)
+ .with(environment, pods: pods, deployments: deployments)
+ .and_return(:mock_rollout_status)
+
+ is_expected.to eq(:mock_rollout_status)
+ end
+ end
+
+ context 'cached rollout status is not present yet' do
+ before do
+ stub_reactive_cache(environment, nil)
+ end
+
+ it 'falls back to a loading status' do
+ expect(::Gitlab::Kubernetes::RolloutStatus).to receive(:loading).and_return(:mock_loading_status)
+
+ is_expected.to eq(:mock_loading_status)
+ end
+ end
+ end
+
+ describe '#ingresses' do
+ subject { environment.ingresses }
+
+ let(:deployment_platform) { double(:deployment_platform) }
+ let(:deployment_namespace) { 'production' }
+
+ before do
+ allow(environment).to receive(:deployment_platform) { deployment_platform }
+ allow(environment).to receive(:deployment_namespace) { deployment_namespace }
+ end
+
+ context 'when rollout status is available' do
+ before do
+ allow(environment).to receive(:rollout_status_available?) { true }
+ end
+
+ it 'fetches ingresses from the deployment platform' do
+ expect(deployment_platform).to receive(:ingresses).with(deployment_namespace)
+
+ subject
+ end
+ end
+
+ context 'when rollout status is not available' do
+ before do
+ allow(environment).to receive(:rollout_status_available?) { false }
+ end
+
+ it 'does nothing' do
+ expect(deployment_platform).not_to receive(:ingresses)
+
+ subject
+ end
+ end
+ end
+
+ describe '#patch_ingress' do
+ subject { environment.patch_ingress(ingress, data) }
+
+ let(:ingress) { double(:ingress) }
+ let(:data) { double(:data) }
+ let(:deployment_platform) { double(:deployment_platform) }
+ let(:deployment_namespace) { 'production' }
+
+ before do
+ allow(environment).to receive(:deployment_platform) { deployment_platform }
+ allow(environment).to receive(:deployment_namespace) { deployment_namespace }
+ end
+
+ context 'when rollout status is available' do
+ before do
+ allow(environment).to receive(:rollout_status_available?) { true }
+ end
+
+ it 'fetches ingresses from the deployment platform' do
+ expect(deployment_platform).to receive(:patch_ingress).with(deployment_namespace, ingress, data)
+
+ subject
+ end
+ end
+
+ context 'when rollout status is not available' do
+ before do
+ allow(environment).to receive(:rollout_status_available?) { false }
+ end
+
+ it 'does nothing' do
+ expect(deployment_platform).not_to receive(:patch_ingress)
+
+ subject
+ end
+ end
+ end
end
diff --git a/spec/models/experiment_spec.rb b/spec/models/experiment_spec.rb
index 587f410c9be..1bf7b8b4850 100644
--- a/spec/models/experiment_spec.rb
+++ b/spec/models/experiment_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Experiment do
describe 'associations' do
it { is_expected.to have_many(:experiment_users) }
+ it { is_expected.to have_many(:experiment_subjects) }
end
describe 'validations' do
@@ -19,20 +20,21 @@ RSpec.describe Experiment do
let_it_be(:experiment_name) { :experiment_key }
let_it_be(:user) { 'a user' }
let_it_be(:group) { 'a group' }
+ let_it_be(:context) { { a: 42 } }
- subject(:add_user) { described_class.add_user(experiment_name, group, user) }
+ subject(:add_user) { described_class.add_user(experiment_name, group, user, context) }
context 'when an experiment with the provided name does not exist' do
it 'creates a new experiment record' do
allow_next_instance_of(described_class) do |experiment|
- allow(experiment).to receive(:record_user_and_group).with(user, group)
+ allow(experiment).to receive(:record_user_and_group).with(user, group, context)
end
expect { add_user }.to change(described_class, :count).by(1)
end
- it 'forwards the user and group_type to the instance' do
+ it 'forwards the user, group_type, and context to the instance' do
expect_next_instance_of(described_class) do |experiment|
- expect(experiment).to receive(:record_user_and_group).with(user, group)
+ expect(experiment).to receive(:record_user_and_group).with(user, group, context)
end
add_user
end
@@ -43,18 +45,95 @@ RSpec.describe Experiment do
it 'does not create a new experiment record' do
allow_next_found_instance_of(described_class) do |experiment|
- allow(experiment).to receive(:record_user_and_group).with(user, group)
+ allow(experiment).to receive(:record_user_and_group).with(user, group, context)
end
expect { add_user }.not_to change(described_class, :count)
end
- it 'forwards the user and group_type to the instance' do
+ it 'forwards the user, group_type, and context to the instance' do
expect_next_found_instance_of(described_class) do |experiment|
- expect(experiment).to receive(:record_user_and_group).with(user, group)
+ expect(experiment).to receive(:record_user_and_group).with(user, group, context)
end
add_user
end
end
+
+ it 'works without the optional context argument' do
+ allow_next_instance_of(described_class) do |experiment|
+ expect(experiment).to receive(:record_user_and_group).with(user, group, {})
+ end
+
+ expect { described_class.add_user(experiment_name, group, user) }.not_to raise_error
+ end
+ end
+
+ describe '.record_conversion_event' do
+ let_it_be(:user) { build(:user) }
+
+ let(:experiment_key) { :test_experiment }
+
+ subject(:record_conversion_event) { described_class.record_conversion_event(experiment_key, user) }
+
+ context 'when no matching experiment exists' do
+ it 'creates the experiment and uses it' do
+ expect_next_instance_of(described_class) do |experiment|
+ expect(experiment).to receive(:record_conversion_event_for_user)
+ end
+ expect { record_conversion_event }.to change { described_class.count }.by(1)
+ end
+
+ context 'but we are unable to successfully create one' do
+ let(:experiment_key) { nil }
+
+ it 'raises a RecordInvalid error' do
+ expect { record_conversion_event }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+
+ context 'when a matching experiment already exists' do
+ before do
+ create(:experiment, name: experiment_key)
+ end
+
+ it 'sends record_conversion_event_for_user to the experiment instance' do
+ expect_next_found_instance_of(described_class) do |experiment|
+ expect(experiment).to receive(:record_conversion_event_for_user).with(user)
+ end
+ record_conversion_event
+ end
+ end
+ end
+
+ describe '#record_conversion_event_for_user' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:experiment) { create(:experiment) }
+
+ subject(:record_conversion_event_for_user) { experiment.record_conversion_event_for_user(user) }
+
+ context 'when no existing experiment_user record exists for the given user' do
+ it 'does not update or create an experiment_user record' do
+ expect { record_conversion_event_for_user }.not_to change { ExperimentUser.all.to_a }
+ end
+ end
+
+ context 'when an existing experiment_user exists for the given user' do
+ context 'but it has already been converted' do
+ let!(:experiment_user) { create(:experiment_user, experiment: experiment, user: user, converted_at: 2.days.ago) }
+
+ it 'does not update the converted_at value' do
+ expect { record_conversion_event_for_user }.not_to change { experiment_user.converted_at }
+ end
+ end
+
+ context 'and it has not yet been converted' do
+ let(:experiment_user) { create(:experiment_user, experiment: experiment, user: user) }
+
+ it 'updates the converted_at value' do
+ expect { record_conversion_event_for_user }.to change { experiment_user.reload.converted_at }
+ end
+ end
+ end
end
describe '#record_user_and_group' do
@@ -62,8 +141,9 @@ RSpec.describe Experiment do
let_it_be(:user) { create(:user) }
let(:group) { :control }
+ let(:context) { { a: 42 } }
- subject(:record_user_and_group) { experiment.record_user_and_group(user, group) }
+ subject(:record_user_and_group) { experiment.record_user_and_group(user, group, context) }
context 'when an experiment_user does not yet exist for the given user' do
it 'creates a new experiment_user record' do
@@ -74,24 +154,35 @@ RSpec.describe Experiment do
record_user_and_group
expect(ExperimentUser.last.group_type).to eq('control')
end
+
+ it 'adds the correct context to the experiment_user' do
+ record_user_and_group
+ expect(ExperimentUser.last.context).to eq({ 'a' => 42 })
+ end
end
context 'when an experiment_user already exists for the given user' do
before do
# Create an existing experiment_user for this experiment and the :control group
- experiment.record_user_and_group(user, :control)
+ experiment.record_user_and_group(user, :control, context)
end
it 'does not create a new experiment_user record' do
expect { record_user_and_group }.not_to change(ExperimentUser, :count)
end
- context 'but the group_type has changed' do
+ context 'but the group_type and context has changed' do
let(:group) { :experimental }
+ let(:context) { { b: 37 } }
- it 'updates the existing experiment_user record' do
+ it 'updates the existing experiment_user record with group_type' do
expect { record_user_and_group }.to change { ExperimentUser.last.group_type }
end
+
+ it 'updates the existing experiment_user record with context' do
+ record_user_and_group
+ expect(ExperimentUser.last.context).to eq({ 'b' => 37 })
+ end
end
end
end
diff --git a/spec/models/experiment_subject_spec.rb b/spec/models/experiment_subject_spec.rb
new file mode 100644
index 00000000000..4850814c5f5
--- /dev/null
+++ b/spec/models/experiment_subject_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ExperimentSubject, type: :model do
+ describe 'associations' do
+ it { is_expected.to belong_to(:experiment) }
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to belong_to(:group) }
+ it { is_expected.to belong_to(:project) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:experiment) }
+
+ describe 'must_have_one_subject_present' do
+ let(:experiment_subject) { build(:experiment_subject, user: nil, group: nil, project: nil) }
+ let(:error_message) { 'Must have exactly one of User, Group, or Project.' }
+
+ it 'fails when no subject is present' do
+ expect(experiment_subject).not_to be_valid
+ expect(experiment_subject.errors[:base]).to include(error_message)
+ end
+
+ it 'passes when user subject is present' do
+ experiment_subject.user = build(:user)
+ expect(experiment_subject).to be_valid
+ end
+
+ it 'passes when group subject is present' do
+ experiment_subject.group = build(:group)
+ expect(experiment_subject).to be_valid
+ end
+
+ it 'passes when project subject is present' do
+ experiment_subject.project = build(:project)
+ expect(experiment_subject).to be_valid
+ end
+
+ it 'fails when more than one subject is present', :aggregate_failures do
+ # two subjects
+ experiment_subject.user = build(:user)
+ experiment_subject.group = build(:group)
+ expect(experiment_subject).not_to be_valid
+ expect(experiment_subject.errors[:base]).to include(error_message)
+
+ # three subjects
+ experiment_subject.project = build(:project)
+ expect(experiment_subject).not_to be_valid
+ expect(experiment_subject.errors[:base]).to include(error_message)
+ end
+ end
+ end
+end
diff --git a/spec/models/exported_protected_branch_spec.rb b/spec/models/exported_protected_branch_spec.rb
new file mode 100644
index 00000000000..7886a522741
--- /dev/null
+++ b/spec/models/exported_protected_branch_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ExportedProtectedBranch do
+ describe 'Associations' do
+ it { is_expected.to have_many(:push_access_levels) }
+ end
+
+ describe '.push_access_levels' do
+ it 'returns the correct push access levels' do
+ exported_branch = create(:exported_protected_branch, :developers_can_push)
+ deploy_key = create(:deploy_key)
+ create(:deploy_keys_project, :write_access, project: exported_branch.project, deploy_key: deploy_key )
+ create(:protected_branch_push_access_level, protected_branch: exported_branch, deploy_key: deploy_key)
+ dev_push_access_level = exported_branch.push_access_levels.first
+
+ expect(exported_branch.push_access_levels).to contain_exactly(dev_push_access_level)
+ end
+ end
+end
diff --git a/spec/models/group_import_state_spec.rb b/spec/models/group_import_state_spec.rb
index 469b5c96ac9..52ea20aeac8 100644
--- a/spec/models/group_import_state_spec.rb
+++ b/spec/models/group_import_state_spec.rb
@@ -70,4 +70,24 @@ RSpec.describe GroupImportState do
end
end
end
+
+ context 'when import failed' do
+ context 'when error message is present' do
+ it 'truncates error message' do
+ group_import_state = build(:group_import_state, :started)
+ group_import_state.fail_op('e' * 300)
+
+ expect(group_import_state.last_error.length).to eq(255)
+ end
+ end
+
+ context 'when error message is missing' do
+ it 'has no error message' do
+ group_import_state = build(:group_import_state, :started)
+ group_import_state.fail_op
+
+ expect(group_import_state.last_error).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index dd1faf999b3..cc8e744a15c 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -30,6 +30,7 @@ RSpec.describe Group do
it { is_expected.to have_many(:services) }
it { is_expected.to have_one(:dependency_proxy_setting) }
it { is_expected.to have_many(:dependency_proxy_blobs) }
+ it { is_expected.to have_many(:dependency_proxy_manifests) }
describe '#members & #requesters' do
let(:requester) { create(:user) }
@@ -798,20 +799,36 @@ RSpec.describe Group do
end
end
- describe '#direct_and_indirect_members' do
+ context 'members-related methods' do
let!(:group) { create(:group, :nested) }
let!(:sub_group) { create(:group, parent: group) }
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
let!(:other_developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
- it 'returns parents members' do
- expect(group.direct_and_indirect_members).to include(developer)
- expect(group.direct_and_indirect_members).to include(maintainer)
+ describe '#direct_and_indirect_members' do
+ it 'returns parents members' do
+ expect(group.direct_and_indirect_members).to include(developer)
+ expect(group.direct_and_indirect_members).to include(maintainer)
+ end
+
+ it 'returns descendant members' do
+ expect(group.direct_and_indirect_members).to include(other_developer)
+ end
end
- it 'returns descendant members' do
- expect(group.direct_and_indirect_members).to include(other_developer)
+ describe '#direct_and_indirect_members_with_inactive' do
+ let!(:maintainer_blocked) { group.parent.add_user(create(:user, :blocked), GroupMember::MAINTAINER) }
+
+ it 'returns parents members' do
+ expect(group.direct_and_indirect_members_with_inactive).to include(developer)
+ expect(group.direct_and_indirect_members_with_inactive).to include(maintainer)
+ expect(group.direct_and_indirect_members_with_inactive).to include(maintainer_blocked)
+ end
+
+ it 'returns descendant members' do
+ expect(group.direct_and_indirect_members_with_inactive).to include(other_developer)
+ end
end
end
@@ -834,7 +851,7 @@ RSpec.describe Group do
end
end
- describe '#direct_and_indirect_users' do
+ context 'user-related methods' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
let(:user_c) { create(:user) }
@@ -853,14 +870,40 @@ RSpec.describe Group do
project.add_developer(user_d)
end
- it 'returns member users on every nest level without duplication' do
- expect(group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c, user_d)
- expect(nested_group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c)
- expect(deep_nested_group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c)
+ describe '#direct_and_indirect_users' do
+ it 'returns member users on every nest level without duplication' do
+ expect(group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c, user_d)
+ expect(nested_group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c)
+ expect(deep_nested_group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c)
+ end
+
+ it 'does not return members of projects belonging to ancestor groups' do
+ expect(nested_group.direct_and_indirect_users).not_to include(user_d)
+ end
end
- it 'does not return members of projects belonging to ancestor groups' do
- expect(nested_group.direct_and_indirect_users).not_to include(user_d)
+ describe '#direct_and_indirect_users_with_inactive' do
+ let(:user_blocked_1) { create(:user, :blocked) }
+ let(:user_blocked_2) { create(:user, :blocked) }
+ let(:user_blocked_3) { create(:user, :blocked) }
+ let(:project_in_group) { create(:project, namespace: nested_group) }
+
+ before do
+ group.add_developer(user_blocked_1)
+ nested_group.add_developer(user_blocked_1)
+ deep_nested_group.add_developer(user_blocked_2)
+ project_in_group.add_developer(user_blocked_3)
+ end
+
+ it 'returns member users on every nest level without duplication' do
+ expect(group.direct_and_indirect_users_with_inactive).to contain_exactly(user_a, user_b, user_c, user_d, user_blocked_1, user_blocked_2, user_blocked_3)
+ expect(nested_group.direct_and_indirect_users_with_inactive).to contain_exactly(user_a, user_b, user_c, user_blocked_1, user_blocked_2, user_blocked_3)
+ expect(deep_nested_group.direct_and_indirect_users_with_inactive).to contain_exactly(user_a, user_b, user_c, user_blocked_1, user_blocked_2)
+ end
+
+ it 'returns members of projects belonging to group' do
+ expect(nested_group.direct_and_indirect_users_with_inactive).to include(user_blocked_3)
+ end
end
end
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index 696d33b7beb..c0efb2dff56 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -81,6 +81,36 @@ RSpec.describe Identity do
end
end
+ describe '.with_any_extern_uid' do
+ context 'provider with extern uid' do
+ let!(:test_entity) { create(:identity, provider: 'test_provider', extern_uid: 'test_uid') }
+
+ it 'finds any extern uids associated with a provider' do
+ identity = described_class.with_any_extern_uid('test_provider').first
+
+ expect(identity).to be
+ end
+ end
+
+ context 'provider with nil extern uid' do
+ let!(:nil_entity) { create(:identity, provider: 'nil_entity_provider', extern_uid: nil) }
+
+ it 'has no results when there are no extern uids' do
+ identity = described_class.with_any_extern_uid('nil_entity_provider').first
+
+ expect(identity).to be_nil
+ end
+ end
+
+ context 'no provider' do
+ it 'has no results when there is no associated provider' do
+ identity = described_class.with_any_extern_uid('nonexistent_provider').first
+
+ expect(identity).to be_nil
+ end
+ end
+ end
+
context 'callbacks' do
context 'before_save' do
describe 'normalizes extern uid' do
diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb
index 383e548c324..d3566ed04c2 100644
--- a/spec/models/instance_configuration_spec.rb
+++ b/spec/models/instance_configuration_spec.rb
@@ -10,31 +10,35 @@ RSpec.describe InstanceConfiguration do
let(:sha256) { 'SHA256:2KJDT7xf2i68mBgJ3TVsjISntg4droLbXYLfQj0VvSY' }
it 'does not return anything if file does not exist' do
- stub_pub_file(exist: false)
+ stub_pub_file(pub_file(exist: false))
expect(subject.settings[:ssh_algorithms_hashes]).to be_empty
end
it 'does not return anything if file is empty' do
- stub_pub_file
+ stub_pub_file(pub_file)
- allow(File).to receive(:read).and_return('')
+ stub_file_read(pub_file, content: '')
expect(subject.settings[:ssh_algorithms_hashes]).to be_empty
end
it 'returns the md5 and sha256 if file valid and exists' do
- stub_pub_file
+ stub_pub_file(pub_file)
result = subject.settings[:ssh_algorithms_hashes].select { |o| o[:md5] == md5 && o[:sha256] == sha256 }
expect(result.size).to eq(InstanceConfiguration::SSH_ALGORITHMS.size)
end
- def stub_pub_file(exist: true)
+ def pub_file(exist: true)
path = exist ? 'spec/fixtures/ssh_host_example_key.pub' : 'spec/fixtures/ssh_host_example_key.pub.random'
- allow(subject).to receive(:ssh_algorithm_file).and_return(Rails.root.join(path))
+ Rails.root.join(path)
+ end
+
+ def stub_pub_file(path)
+ allow(subject).to receive(:ssh_algorithm_file).and_return(path)
end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 16ea2989eda..81f045b4db1 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -141,7 +141,6 @@ RSpec.describe Issue do
describe '.with_issue_type' do
let_it_be(:issue) { create(:issue, project: reusable_project) }
let_it_be(:incident) { create(:incident, project: reusable_project) }
- let_it_be(:test_case) { create(:quality_test_case, project: reusable_project) }
it 'gives issues with the given issue type' do
expect(described_class.with_issue_type('issue'))
@@ -149,8 +148,8 @@ RSpec.describe Issue do
end
it 'gives issues with the given issue type' do
- expect(described_class.with_issue_type(%w(issue incident test_case)))
- .to contain_exactly(issue, incident, test_case)
+ expect(described_class.with_issue_type(%w(issue incident)))
+ .to contain_exactly(issue, incident)
end
end
@@ -370,6 +369,20 @@ RSpec.describe Issue do
expect(link_types).not_to include(nil)
end
+ it 'returns issues including the link creation time' do
+ dates = authorized_issue_a.related_issues(user).map(&:issue_link_created_at)
+
+ expect(dates).not_to be_empty
+ expect(dates).not_to include(nil)
+ end
+
+ it 'returns issues including the link update time' do
+ dates = authorized_issue_a.related_issues(user).map(&:issue_link_updated_at)
+
+ expect(dates).not_to be_empty
+ expect(dates).not_to include(nil)
+ end
+
describe 'when a user cannot read cross project' do
it 'only returns issues within the same project' do
expect(Ability).to receive(:allowed?).with(user, :read_all_resources, :global).at_least(:once).and_call_original
diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb
index db961d5a4e6..adeccd999f3 100644
--- a/spec/models/label_priority_spec.rb
+++ b/spec/models/label_priority_spec.rb
@@ -18,5 +18,11 @@ RSpec.describe LabelPriority do
expect(subject).to validate_uniqueness_of(:label_id).scoped_to(:project_id)
end
+
+ describe 'when importing' do
+ subject { create(:label_priority, importing: true) }
+
+ it { is_expected.not_to validate_presence_of(:label) }
+ end
end
end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 6706083fd91..a5493d1650b 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -9,6 +9,10 @@ RSpec.describe MergeRequestDiff do
let(:diff_with_commits) { create(:merge_request).merge_request_diff }
+ before do
+ stub_feature_flags(diffs_gradual_load: false)
+ end
+
describe 'validations' do
subject { diff_with_commits }
@@ -415,7 +419,7 @@ RSpec.describe MergeRequestDiff do
context 'when persisted files available' do
it 'returns paginated diffs' do
- diffs = diff_with_commits.diffs_in_batch(1, 10, diff_options: {})
+ diffs = diff_with_commits.diffs_in_batch(1, 10, diff_options: diff_options)
expect(diffs).to be_a(Gitlab::Diff::FileCollection::MergeRequestDiffBatch)
expect(diffs.diff_files.size).to eq(10)
@@ -423,6 +427,150 @@ RSpec.describe MergeRequestDiff do
next_page: 2,
total_pages: 2)
end
+
+ it 'sorts diff files directory first' do
+ diff_with_commits.update!(sorted: false) # Mark as unsorted so it'll re-order
+
+ expect(diff_with_commits.diffs_in_batch(1, 10, diff_options: diff_options).diff_file_paths).to eq([
+ 'bar/branch-test.txt',
+ 'custom-highlighting/test.gitlab-custom',
+ 'encoding/iso8859.txt',
+ 'files/images/wm.svg',
+ 'files/js/commit.coffee',
+ 'files/lfs/lfs_object.iso',
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/.DS_Store',
+ 'files/whitespace'
+ ])
+ end
+
+ context 'when sort_diffs feature flag is disabled' do
+ before do
+ stub_feature_flags(sort_diffs: false)
+ end
+
+ it 'does not sort diff files directory first' do
+ expect(diff_with_commits.diffs_in_batch(1, 10, diff_options: diff_options).diff_file_paths).to eq([
+ '.DS_Store',
+ '.gitattributes',
+ '.gitignore',
+ '.gitmodules',
+ 'CHANGELOG',
+ 'README',
+ 'bar/branch-test.txt',
+ 'custom-highlighting/test.gitlab-custom',
+ 'encoding/iso8859.txt',
+ 'files/.DS_Store'
+ ])
+ end
+ end
+ end
+ end
+
+ describe '#diffs' do
+ let(:diff_options) { {} }
+
+ shared_examples_for 'fetching full diffs' do
+ it 'returns diffs from repository comparison' do
+ expect_next_instance_of(Compare) do |comparison|
+ expect(comparison).to receive(:diffs)
+ .with(diff_options)
+ .and_call_original
+ end
+
+ diff_with_commits.diffs(diff_options)
+ end
+
+ it 'returns a Gitlab::Diff::FileCollection::Compare with full diffs' do
+ diffs = diff_with_commits.diffs(diff_options)
+
+ expect(diffs).to be_a(Gitlab::Diff::FileCollection::Compare)
+ expect(diffs.diff_files.size).to eq(20)
+ end
+ end
+
+ context 'when no persisted files available' do
+ before do
+ diff_with_commits.clean!
+ end
+
+ it_behaves_like 'fetching full diffs'
+ end
+
+ context 'when diff_options include ignore_whitespace_change' do
+ it_behaves_like 'fetching full diffs' do
+ let(:diff_options) do
+ { ignore_whitespace_change: true }
+ end
+ end
+ end
+
+ context 'when persisted files available' do
+ it 'returns diffs' do
+ diffs = diff_with_commits.diffs(diff_options)
+
+ expect(diffs).to be_a(Gitlab::Diff::FileCollection::MergeRequestDiff)
+ expect(diffs.diff_files.size).to eq(20)
+ end
+
+ it 'sorts diff files directory first' do
+ diff_with_commits.update!(sorted: false) # Mark as unsorted so it'll re-order
+
+ expect(diff_with_commits.diffs(diff_options).diff_file_paths).to eq([
+ 'bar/branch-test.txt',
+ 'custom-highlighting/test.gitlab-custom',
+ 'encoding/iso8859.txt',
+ 'files/images/wm.svg',
+ 'files/js/commit.coffee',
+ 'files/lfs/lfs_object.iso',
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/.DS_Store',
+ 'files/whitespace',
+ 'foo/bar/.gitkeep',
+ 'with space/README.md',
+ '.DS_Store',
+ '.gitattributes',
+ '.gitignore',
+ '.gitmodules',
+ 'CHANGELOG',
+ 'README',
+ 'gitlab-grack',
+ 'gitlab-shell'
+ ])
+ end
+
+ context 'when sort_diffs feature flag is disabled' do
+ before do
+ stub_feature_flags(sort_diffs: false)
+ end
+
+ it 'does not sort diff files directory first' do
+ expect(diff_with_commits.diffs(diff_options).diff_file_paths).to eq([
+ '.DS_Store',
+ '.gitattributes',
+ '.gitignore',
+ '.gitmodules',
+ 'CHANGELOG',
+ 'README',
+ 'bar/branch-test.txt',
+ 'custom-highlighting/test.gitlab-custom',
+ 'encoding/iso8859.txt',
+ 'files/.DS_Store',
+ 'files/images/wm.svg',
+ 'files/js/commit.coffee',
+ 'files/lfs/lfs_object.iso',
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/whitespace',
+ 'foo/bar/.gitkeep',
+ 'gitlab-grack',
+ 'gitlab-shell',
+ 'with space/README.md'
+ ])
+ end
+ end
end
end
@@ -501,6 +649,68 @@ RSpec.describe MergeRequestDiff do
expect(mr_diff.empty?).to be_truthy
end
+ it 'persists diff files sorted directory first' do
+ mr_diff = create(:merge_request).merge_request_diff
+ diff_files_paths = mr_diff.merge_request_diff_files.map { |file| file.new_path.presence || file.old_path }
+
+ expect(diff_files_paths).to eq([
+ 'bar/branch-test.txt',
+ 'custom-highlighting/test.gitlab-custom',
+ 'encoding/iso8859.txt',
+ 'files/images/wm.svg',
+ 'files/js/commit.coffee',
+ 'files/lfs/lfs_object.iso',
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/.DS_Store',
+ 'files/whitespace',
+ 'foo/bar/.gitkeep',
+ 'with space/README.md',
+ '.DS_Store',
+ '.gitattributes',
+ '.gitignore',
+ '.gitmodules',
+ 'CHANGELOG',
+ 'README',
+ 'gitlab-grack',
+ 'gitlab-shell'
+ ])
+ end
+
+ context 'when sort_diffs feature flag is disabled' do
+ before do
+ stub_feature_flags(sort_diffs: false)
+ end
+
+ it 'persists diff files unsorted by directory first' do
+ mr_diff = create(:merge_request).merge_request_diff
+ diff_files_paths = mr_diff.merge_request_diff_files.map { |file| file.new_path.presence || file.old_path }
+
+ expect(diff_files_paths).to eq([
+ '.DS_Store',
+ '.gitattributes',
+ '.gitignore',
+ '.gitmodules',
+ 'CHANGELOG',
+ 'README',
+ 'bar/branch-test.txt',
+ 'custom-highlighting/test.gitlab-custom',
+ 'encoding/iso8859.txt',
+ 'files/.DS_Store',
+ 'files/images/wm.svg',
+ 'files/js/commit.coffee',
+ 'files/lfs/lfs_object.iso',
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/whitespace',
+ 'foo/bar/.gitkeep',
+ 'gitlab-grack',
+ 'gitlab-shell',
+ 'with space/README.md'
+ ])
+ end
+ end
+
it 'expands collapsed diffs before saving' do
mr_diff = create(:merge_request, source_branch: 'expand-collapse-lines', target_branch: 'master').merge_request_diff
diff_file = mr_diff.merge_request_diff_files.find_by(new_path: 'expand-collapse/file-5.txt')
diff --git a/spec/models/merge_request_reviewer_spec.rb b/spec/models/merge_request_reviewer_spec.rb
index 55199cd96ad..76b44abca54 100644
--- a/spec/models/merge_request_reviewer_spec.rb
+++ b/spec/models/merge_request_reviewer_spec.rb
@@ -9,6 +9,6 @@ RSpec.describe MergeRequestReviewer do
describe 'associations' do
it { is_expected.to belong_to(:merge_request).class_name('MergeRequest') }
- it { is_expected.to belong_to(:reviewer).class_name('User') }
+ it { is_expected.to belong_to(:reviewer).class_name('User').inverse_of(:merge_request_reviewers) }
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 9574c57e46c..431a60a11a5 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -92,6 +92,39 @@ RSpec.describe MergeRequest, factory_default: :keep do
it { is_expected.not_to include(mr_without_jira_reference) }
end
+ context 'scopes' do
+ let_it_be(:user1) { create(:user) }
+ let_it_be(:user2) { create(:user) }
+
+ let_it_be(:merge_request1) { create(:merge_request, :unique_branches, reviewers: [user1])}
+ let_it_be(:merge_request2) { create(:merge_request, :unique_branches, reviewers: [user2])}
+ let_it_be(:merge_request3) { create(:merge_request, :unique_branches, reviewers: [])}
+
+ describe '.review_requested' do
+ it 'returns MRs that has any review requests' do
+ expect(described_class.review_requested).to eq([merge_request1, merge_request2])
+ end
+ end
+
+ describe '.no_review_requested' do
+ it 'returns MRs that has no review requests' do
+ expect(described_class.no_review_requested).to eq([merge_request3])
+ end
+ end
+
+ describe '.review_requested_to' do
+ it 'returns MRs that the user has been requested to review' do
+ expect(described_class.review_requested_to(user1)).to eq([merge_request1])
+ end
+ end
+
+ describe '.no_review_requested_to' do
+ it 'returns MRs that the user has been requested to review' do
+ expect(described_class.no_review_requested_to(user1)).to eq([merge_request2, merge_request3])
+ end
+ end
+ end
+
describe '#squash_in_progress?' do
let(:repo_path) do
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
@@ -467,6 +500,77 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
+ describe 'time to merge calculations' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+
+ let!(:mr1) do
+ create(
+ :merge_request,
+ :with_merged_metrics,
+ source_project: project,
+ target_project: project
+ )
+ end
+
+ let!(:mr2) do
+ create(
+ :merge_request,
+ :with_merged_metrics,
+ source_project: project,
+ target_project: project
+ )
+ end
+
+ let!(:mr3) do
+ create(
+ :merge_request,
+ :with_merged_metrics,
+ source_project: project,
+ target_project: project
+ )
+ end
+
+ let!(:unmerged_mr) do
+ create(
+ :merge_request,
+ source_project: project,
+ target_project: project
+ )
+ end
+
+ before do
+ project.add_user(user, :developer)
+ end
+
+ describe '.total_time_to_merge' do
+ it 'returns the sum of the time to merge for all merged MRs' do
+ mrs = project.merge_requests
+
+ expect(mrs.total_time_to_merge).to be_within(1).of(expected_total_time(mrs))
+ end
+
+ context 'when merged_at is earlier than created_at' do
+ before do
+ mr1.metrics.update!(merged_at: mr1.metrics.created_at - 1.week)
+ end
+
+ it 'returns nil' do
+ mrs = project.merge_requests.where(id: mr1.id)
+
+ expect(mrs.total_time_to_merge).to be_nil
+ end
+ end
+
+ def expected_total_time(mrs)
+ mrs = mrs.reject { |mr| mr.merged_at.nil? }
+ mrs.reduce(0.0) do |sum, mr|
+ (mr.merged_at - mr.created_at) + sum
+ end
+ end
+ end
+ end
+
describe '#target_branch_sha' do
let(:project) { create(:project, :repository) }
@@ -1825,6 +1929,32 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
+ describe '#has_codequality_reports?' do
+ subject { merge_request.has_codequality_reports? }
+
+ let(:project) { create(:project, :repository) }
+
+ context 'when head pipeline has a codequality report' do
+ let(:merge_request) { create(:merge_request, :with_codequality_reports, source_project: project) }
+
+ it { is_expected.to be_truthy }
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(codequality_mr_diff: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when head pipeline does not have a codequality report' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#has_terraform_reports?' do
context 'when head pipeline has terraform reports' do
it 'returns true' do
@@ -2101,6 +2231,62 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
+ describe '#compare_codequality_reports' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:merge_request, reload: true) { create(:merge_request, :with_codequality_reports, source_project: project) }
+ let_it_be(:pipeline) { merge_request.head_pipeline }
+
+ subject { merge_request.compare_codequality_reports }
+
+ context 'when head pipeline has codequality report' do
+ let(:job) do
+ create(:ci_build, options: { artifacts: { reports: { codeclimate: ['codequality.json'] } } }, pipeline: pipeline)
+ end
+
+ let(:artifacts_metadata) { create(:ci_job_artifact, :metadata, job: job) }
+
+ context 'when reactive cache worker is parsing results asynchronously' do
+ it 'returns parsing status' do
+ expect(subject[:status]).to eq(:parsing)
+ end
+ end
+
+ context 'when reactive cache worker is inline' do
+ before do
+ synchronous_reactive_cache(merge_request)
+ end
+
+ it 'returns parsed status' do
+ expect(subject[:status]).to eq(:parsed)
+ expect(subject[:data]).to be_present
+ end
+
+ context 'when an error occurrs' do
+ before do
+ merge_request.update!(head_pipeline: nil)
+ end
+
+ it 'returns an error status' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:status_reason]).to eq("This merge request does not have codequality reports")
+ end
+ end
+
+ context 'when cached result is not latest' do
+ before do
+ allow_next_instance_of(Ci::CompareCodequalityReportsService) do |service|
+ allow(service).to receive(:latest?).and_return(false)
+ end
+ end
+
+ it 'raises an InvalidateReactiveCache error' do
+ expect { subject }.to raise_error(ReactiveCaching::InvalidateReactiveCache)
+ end
+ end
+ end
+ end
+ end
+
describe '#all_commit_shas' do
context 'when merge request is persisted' do
let(:all_commit_shas) do
@@ -3266,7 +3452,7 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
- describe "#closed_without_fork?" do
+ describe "#closed_or_merged_without_fork?" do
let(:project) { create(:project) }
let(:forked_project) { fork_project(project) }
let(:user) { create(:user) }
@@ -3280,14 +3466,33 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
it "returns false if the fork exist" do
- expect(closed_merge_request.closed_without_fork?).to be_falsey
+ expect(closed_merge_request.closed_or_merged_without_fork?).to be_falsey
end
it "returns true if the fork does not exist" do
unlink_project.execute
closed_merge_request.reload
- expect(closed_merge_request.closed_without_fork?).to be_truthy
+ expect(closed_merge_request.closed_or_merged_without_fork?).to be_truthy
+ end
+ end
+
+ context "when the merge request was merged" do
+ let(:merged_merge_request) do
+ create(:merged_merge_request,
+ source_project: forked_project,
+ target_project: project)
+ end
+
+ it "returns false if the fork exist" do
+ expect(merged_merge_request.closed_or_merged_without_fork?).to be_falsey
+ end
+
+ it "returns true if the fork does not exist" do
+ unlink_project.execute
+ merged_merge_request.reload
+
+ expect(merged_merge_request.closed_or_merged_without_fork?).to be_truthy
end
end
@@ -3299,7 +3504,7 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
it "returns false" do
- expect(open_merge_request.closed_without_fork?).to be_falsey
+ expect(open_merge_request.closed_or_merged_without_fork?).to be_falsey
end
end
end
@@ -4242,6 +4447,14 @@ RSpec.describe MergeRequest, factory_default: :keep do
expect(subject.diffable_merge_ref?).to eq(true)
end
+ context 'merge request is merged' do
+ subject { build_stubbed(:merge_request, :merged, project: project) }
+
+ it 'returns false' do
+ expect(subject.diffable_merge_ref?).to eq(false)
+ end
+ end
+
context 'merge request cannot be merged' do
before do
subject.mark_as_unchecked!
diff --git a/spec/models/namespace_onboarding_action_spec.rb b/spec/models/namespace_onboarding_action_spec.rb
new file mode 100644
index 00000000000..70dcb989b32
--- /dev/null
+++ b/spec/models/namespace_onboarding_action_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe NamespaceOnboardingAction do
+ let(:namespace) { build(:namespace) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:namespace).required }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:action) }
+ end
+
+ describe '.completed?' do
+ let(:action) { :subscription_created }
+
+ subject { described_class.completed?(namespace, action) }
+
+ context 'action created for the namespace' do
+ before do
+ create(:namespace_onboarding_action, namespace: namespace, action: action)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'action created for another namespace' do
+ before do
+ create(:namespace_onboarding_action, namespace: build(:namespace), action: action)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '.create_action' do
+ let(:action) { :subscription_created }
+
+ subject(:create_action) { described_class.create_action(namespace, action) }
+
+ it 'creates the action for the namespace just once' do
+ expect { create_action }.to change { count_namespace_actions }.by(1)
+
+ expect { create_action }.to change { count_namespace_actions }.by(0)
+ end
+
+ def count_namespace_actions
+ described_class.where(namespace: namespace, action: action).count
+ end
+ end
+end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 85f9005052e..0130618d004 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -19,6 +19,7 @@ RSpec.describe Namespace do
it { is_expected.to have_one :aggregation_schedule }
it { is_expected.to have_one :namespace_settings }
it { is_expected.to have_many :custom_emoji }
+ it { is_expected.to have_many :namespace_onboarding_actions }
end
describe 'validations' do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index a3417ee5fc7..6e87ca6dcf7 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -357,7 +357,7 @@ RSpec.describe Note do
describe '#confidential?' do
context 'when note is not confidential' do
context 'when include_noteable is set to true' do
- it 'is true when a noteable is confidential ' do
+ it 'is true when a noteable is confidential' do
issue = create(:issue, :confidential)
note = build(:note, noteable: issue, project: issue.project)
@@ -366,7 +366,7 @@ RSpec.describe Note do
end
context 'when include_noteable is not set to true' do
- it 'is false when a noteable is confidential ' do
+ it 'is false when a noteable is confidential' do
issue = create(:issue, :confidential)
note = build(:note, noteable: issue, project: issue.project)
diff --git a/spec/models/packages/package_file_spec.rb b/spec/models/packages/package_file_spec.rb
index ef09fb037e9..82ac159b9cc 100644
--- a/spec/models/packages/package_file_spec.rb
+++ b/spec/models/packages/package_file_spec.rb
@@ -61,14 +61,14 @@ RSpec.describe Packages::PackageFile, type: :model do
end
end
- describe '#update_file_metadata callback' do
+ describe '#update_file_store callback' do
let_it_be(:package_file) { build(:package_file, :nuget, size: nil) }
subject { package_file.save! }
it 'updates metadata columns' do
expect(package_file)
- .to receive(:update_file_metadata)
+ .to receive(:update_file_store)
.and_call_original
# This expectation uses a stub because we can no longer test a change from
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index 705a1991845..16764673474 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -111,6 +111,24 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value('%foo%bar').for(:name) }
end
+ context 'debian package' do
+ subject { build(:debian_package) }
+
+ it { is_expected.to allow_value('0ad').for(:name) }
+ it { is_expected.to allow_value('g++').for(:name) }
+ it { is_expected.not_to allow_value('a_b').for(:name) }
+ end
+
+ context 'debian incoming' do
+ subject { create(:debian_incoming) }
+
+ # Only 'incoming' is accepted
+ it { is_expected.to allow_value('incoming').for(:name) }
+ it { is_expected.not_to allow_value('0ad').for(:name) }
+ it { is_expected.not_to allow_value('g++').for(:name) }
+ it { is_expected.not_to allow_value('a_b').for(:name) }
+ end
+
context 'generic package' do
subject { build_stubbed(:generic_package) }
@@ -180,6 +198,21 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to allow_value('2.x-dev').for(:version) }
end
+ context 'debian package' do
+ subject { build(:debian_package) }
+
+ it { is_expected.to allow_value('2:4.9.5+dfsg-5+deb10u1').for(:version) }
+ it { is_expected.not_to allow_value('1_0').for(:version) }
+ end
+
+ context 'debian incoming' do
+ subject { create(:debian_incoming) }
+
+ it { is_expected.to allow_value(nil).for(:version) }
+ it { is_expected.not_to allow_value('2:4.9.5+dfsg-5+deb10u1').for(:version) }
+ it { is_expected.not_to allow_value('1_0').for(:version) }
+ end
+
context 'maven package' do
subject { build_stubbed(:maven_package) }
@@ -621,6 +654,46 @@ RSpec.describe Packages::Package, type: :model do
end
end
+ describe '#debian_incoming?' do
+ let(:package) { build(:package) }
+
+ subject { package.debian_incoming? }
+
+ it { is_expected.to eq(false) }
+
+ context 'with debian_incoming' do
+ let(:package) { create(:debian_incoming) }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'with debian_package' do
+ let(:package) { create(:debian_package) }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '#debian_package?' do
+ let(:package) { build(:package) }
+
+ subject { package.debian_package? }
+
+ it { is_expected.to eq(false) }
+
+ context 'with debian_incoming' do
+ let(:package) { create(:debian_incoming) }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'with debian_package' do
+ let(:package) { create(:debian_package) }
+
+ it { is_expected.to eq(true) }
+ end
+ end
+
describe 'plan_limits' do
Packages::Package.package_types.keys.without('composer').each do |pt|
plan_limit_name = if pt == 'generic'
diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb
index f8ebc237577..30712af6b32 100644
--- a/spec/models/pages/lookup_path_spec.rb
+++ b/spec/models/pages/lookup_path_spec.rb
@@ -9,7 +9,6 @@ RSpec.describe Pages::LookupPath do
before do
stub_pages_setting(access_control: true, external_https: ["1.1.1.1:443"])
- stub_artifacts_object_storage
stub_pages_object_storage(::Pages::DeploymentUploader)
end
@@ -66,7 +65,7 @@ RSpec.describe Pages::LookupPath do
end
it 'uses deployment from object storage' do
- Timecop.freeze do
+ freeze_time do
expect(source).to(
eq({
type: 'zip',
@@ -86,7 +85,7 @@ RSpec.describe Pages::LookupPath do
end
it 'uses file protocol' do
- Timecop.freeze do
+ freeze_time do
expect(source).to(
eq({
type: 'zip',
@@ -117,64 +116,6 @@ RSpec.describe Pages::LookupPath do
include_examples 'uses disk storage'
end
end
-
- context 'when artifact_id from build job is present in pages metadata' do
- let(:artifacts_archive) { create(:ci_job_artifact, :zip, :remote_store, project: project) }
-
- before do
- project.mark_pages_as_deployed(artifacts_archive: artifacts_archive)
- end
-
- it 'uses artifacts object storage' do
- Timecop.freeze do
- expect(source).to(
- eq({
- type: 'zip',
- path: artifacts_archive.file.url(expire_at: 1.day.from_now),
- global_id: "gid://gitlab/Ci::JobArtifact/#{artifacts_archive.id}",
- sha256: artifacts_archive.file_sha256,
- file_size: artifacts_archive.size,
- file_count: nil
- })
- )
- end
- end
-
- context 'when artifact is not uploaded to object storage' do
- let(:artifacts_archive) { create(:ci_job_artifact, :zip) }
-
- it 'uses file protocol', :aggregate_failures do
- Timecop.freeze do
- expect(source).to(
- eq({
- type: 'zip',
- path: 'file://' + artifacts_archive.file.path,
- global_id: "gid://gitlab/Ci::JobArtifact/#{artifacts_archive.id}",
- sha256: artifacts_archive.file_sha256,
- file_size: artifacts_archive.size,
- file_count: nil
- })
- )
- end
- end
-
- context 'when pages_serve_with_zip_file_protocol feature flag is disabled' do
- before do
- stub_feature_flags(pages_serve_with_zip_file_protocol: false)
- end
-
- include_examples 'uses disk storage'
- end
- end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(pages_serve_from_artifacts_archive: false)
- end
-
- include_examples 'uses disk storage'
- end
- end
end
describe '#prefix' do
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index c927c8fd1f9..37402fa04c4 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe ProjectFeature do
end
context 'public features' do
- features = %w(issues wiki builds merge_requests snippets repository metrics_dashboard)
+ features = %w(issues wiki builds merge_requests snippets repository metrics_dashboard operations)
features.each do |feature|
it "does not allow public access level for #{feature}" do
diff --git a/spec/models/project_feature_usage_spec.rb b/spec/models/project_feature_usage_spec.rb
index d55d41fab85..cd70602fc4d 100644
--- a/spec/models/project_feature_usage_spec.rb
+++ b/spec/models/project_feature_usage_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe ProjectFeatureUsage, type: :model do
subject { project.feature_usage }
it 'logs Jira DVCS Cloud last sync' do
- Timecop.freeze do
+ freeze_time do
subject.log_jira_dvcs_integration_usage
expect(subject.jira_dvcs_server_last_sync_at).to be_nil
@@ -34,7 +34,7 @@ RSpec.describe ProjectFeatureUsage, type: :model do
end
it 'logs Jira DVCS Server last sync' do
- Timecop.freeze do
+ freeze_time do
subject.log_jira_dvcs_integration_usage(cloud: false)
expect(subject.jira_dvcs_server_last_sync_at).to be_like_time(Time.current)
diff --git a/spec/models/project_repository_storage_move_spec.rb b/spec/models/project_repository_storage_move_spec.rb
index d32867efb39..88535f6dd6e 100644
--- a/spec/models/project_repository_storage_move_spec.rb
+++ b/spec/models/project_repository_storage_move_spec.rb
@@ -3,102 +3,30 @@
require 'spec_helper'
RSpec.describe ProjectRepositoryStorageMove, type: :model do
- describe 'associations' do
- it { is_expected.to belong_to(:project) }
- end
-
- describe 'validations' do
- it { is_expected.to validate_presence_of(:project) }
- it { is_expected.to validate_presence_of(:state) }
- it { is_expected.to validate_presence_of(:source_storage_name) }
- it { is_expected.to validate_presence_of(:destination_storage_name) }
-
- context 'source_storage_name inclusion' do
- subject { build(:project_repository_storage_move, source_storage_name: 'missing') }
-
- it "does not allow repository storages that don't match a label in the configuration" do
- expect(subject).not_to be_valid
- expect(subject.errors[:source_storage_name].first).to match(/is not included in the list/)
- end
- end
-
- context 'destination_storage_name inclusion' do
- subject { build(:project_repository_storage_move, destination_storage_name: 'missing') }
-
- it "does not allow repository storages that don't match a label in the configuration" do
- expect(subject).not_to be_valid
- expect(subject.errors[:destination_storage_name].first).to match(/is not included in the list/)
- end
- end
-
- context 'project repository read-only' do
- subject { build(:project_repository_storage_move, project: project) }
-
- let(:project) { build(:project, repository_read_only: true) }
+ let_it_be_with_refind(:project) { create(:project) }
- it "does not allow the project to be read-only on create" do
- expect(subject).not_to be_valid
- expect(subject.errors[:project].first).to match(/is read only/)
- end
- end
- end
-
- describe 'defaults' do
- context 'destination_storage_name' do
- subject { build(:project_repository_storage_move) }
-
- it 'picks storage from ApplicationSetting' do
- expect(Gitlab::CurrentSettings).to receive(:pick_repository_storage).and_return('picked').at_least(:once)
-
- expect(subject.destination_storage_name).to eq('picked')
- end
- end
+ it_behaves_like 'handles repository moves' do
+ let(:container) { project }
+ let(:repository_storage_factory_key) { :project_repository_storage_move }
+ let(:error_key) { :project }
+ let(:repository_storage_worker) { ProjectUpdateRepositoryStorageWorker }
end
describe 'state transitions' do
- let(:project) { create(:project) }
+ let(:storage) { 'test_second_storage' }
before do
- stub_storage_settings('test_second_storage' => { 'path' => 'tmp/tests/extra_storage' })
- end
-
- context 'when in the default state' do
- subject(:storage_move) { create(:project_repository_storage_move, project: project, destination_storage_name: 'test_second_storage') }
-
- context 'and transits to scheduled' do
- it 'triggers ProjectUpdateRepositoryStorageWorker' do
- expect(ProjectUpdateRepositoryStorageWorker).to receive(:perform_async).with(project.id, 'test_second_storage', storage_move.id)
-
- storage_move.schedule!
-
- expect(project).to be_repository_read_only
- end
- end
-
- context 'and transits to started' do
- it 'does not allow the transition' do
- expect { storage_move.start! }
- .to raise_error(StateMachines::InvalidTransition)
- end
- end
+ stub_storage_settings(storage => { 'path' => 'tmp/tests/extra_storage' })
end
context 'when started' do
- subject(:storage_move) { create(:project_repository_storage_move, :started, project: project, destination_storage_name: 'test_second_storage') }
+ subject(:storage_move) { create(:project_repository_storage_move, :started, container: project, destination_storage_name: storage) }
context 'and transits to replicated' do
- it 'sets the repository storage and marks the project as writable' do
+ it 'sets the repository storage and marks the container as writable' do
storage_move.finish_replication!
- expect(project.repository_storage).to eq('test_second_storage')
- expect(project).not_to be_repository_read_only
- end
- end
-
- context 'and transits to failed' do
- it 'marks the project as writable' do
- storage_move.do_fail!
-
+ expect(project.repository_storage).to eq(storage)
expect(project).not_to be_repository_read_only
end
end
diff --git a/spec/models/project_services/datadog_service_spec.rb b/spec/models/project_services/datadog_service_spec.rb
new file mode 100644
index 00000000000..1d9f49e4824
--- /dev/null
+++ b/spec/models/project_services/datadog_service_spec.rb
@@ -0,0 +1,179 @@
+# frozen_string_literal: true
+require 'securerandom'
+
+require 'spec_helper'
+
+RSpec.describe DatadogService, :model do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:build) { create(:ci_build, project: project) }
+
+ let(:active) { true }
+ let(:dd_site) { 'datadoghq.com' }
+ let(:default_url) { 'https://webhooks-http-intake.logs.datadoghq.com/v1/input/' }
+ let(:api_url) { nil }
+ let(:api_key) { SecureRandom.hex(32) }
+ let(:dd_env) { 'ci' }
+ let(:dd_service) { 'awesome-gitlab' }
+
+ let(:expected_hook_url) { default_url + api_key + "?env=#{dd_env}&service=#{dd_service}" }
+
+ let(:instance) do
+ described_class.new(
+ active: active,
+ project: project,
+ properties: {
+ datadog_site: dd_site,
+ api_url: api_url,
+ api_key: api_key,
+ datadog_env: dd_env,
+ datadog_service: dd_service
+ }
+ )
+ end
+
+ let(:saved_instance) do
+ instance.save!
+ instance
+ end
+
+ let(:pipeline_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
+ let(:build_data) { Gitlab::DataBuilder::Build.build(build) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_one(:service_hook) }
+ end
+
+ describe 'validations' do
+ subject { instance }
+
+ context 'when service is active' do
+ let(:active) { true }
+
+ it { is_expected.to validate_presence_of(:api_key) }
+ it { is_expected.to allow_value(api_key).for(:api_key) }
+ it { is_expected.not_to allow_value('87dab2403c9d462 87aec4d9214edb1e').for(:api_key) }
+ it { is_expected.not_to allow_value('................................').for(:api_key) }
+
+ context 'when selecting site' do
+ let(:dd_site) { 'datadoghq.com' }
+ let(:api_url) { nil }
+
+ it { is_expected.to validate_presence_of(:datadog_site) }
+ it { is_expected.not_to validate_presence_of(:api_url) }
+ it { is_expected.not_to allow_value('datadog hq.com').for(:datadog_site) }
+ end
+
+ context 'with custom api_url' do
+ let(:dd_site) { nil }
+ let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/v1/input/' }
+
+ it { is_expected.not_to validate_presence_of(:datadog_site) }
+ it { is_expected.to validate_presence_of(:api_url) }
+ it { is_expected.to allow_value(api_url).for(:api_url) }
+ it { is_expected.not_to allow_value('example.com').for(:api_url) }
+ end
+
+ context 'when missing site and api_url' do
+ let(:dd_site) { nil }
+ let(:api_url) { nil }
+
+ it { is_expected.not_to be_valid }
+ it { is_expected.to validate_presence_of(:datadog_site) }
+ it { is_expected.to validate_presence_of(:api_url) }
+ end
+ end
+
+ context 'when service is not active' do
+ let(:active) { false }
+
+ it { is_expected.to be_valid }
+ it { is_expected.not_to validate_presence_of(:api_key) }
+ end
+ end
+
+ describe '#hook_url' do
+ subject { instance.hook_url }
+
+ context 'with standard site URL' do
+ it { is_expected.to eq(expected_hook_url) }
+ end
+
+ context 'with custom URL' do
+ let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/v1/input/' }
+
+ it { is_expected.to eq(api_url + api_key + "?env=#{dd_env}&service=#{dd_service}") }
+
+ context 'blank' do
+ let(:api_url) { '' }
+
+ it { is_expected.to eq(expected_hook_url) }
+ end
+ end
+
+ context 'without optional params' do
+ let(:dd_service) { nil }
+ let(:dd_env) { nil }
+
+ it { is_expected.to eq(default_url + api_key) }
+ end
+ end
+
+ describe '#api_keys_url' do
+ subject { instance.api_keys_url }
+
+ it { is_expected.to eq("https://app.#{dd_site}/account/settings#api") }
+
+ context 'with unset datadog_site' do
+ let(:dd_site) { nil }
+
+ it { is_expected.to eq("https://docs.datadoghq.com/account_management/api-app-keys/") }
+ end
+ end
+
+ describe '#test' do
+ context 'when request is succesful' do
+ subject { saved_instance.test(pipeline_data) }
+
+ before do
+ stub_request(:post, expected_hook_url).to_return(body: 'OK')
+ end
+ it { is_expected.to eq({ success: true, result: 'OK' }) }
+ end
+
+ context 'when request fails' do
+ subject { saved_instance.test(pipeline_data) }
+
+ before do
+ stub_request(:post, expected_hook_url).to_return(body: 'CRASH!!!', status: 500)
+ end
+ it { is_expected.to eq({ success: false, result: 'CRASH!!!' }) }
+ end
+ end
+
+ describe '#execute' do
+ before do
+ stub_request(:post, expected_hook_url)
+ saved_instance.execute(data)
+ end
+
+ context 'with pipeline data' do
+ let(:data) { pipeline_data }
+ let(:expected_headers) do
+ { WebHookService::GITLAB_EVENT_HEADER => 'Pipeline Hook' }
+ end
+
+ it { expect(a_request(:post, expected_hook_url).with(headers: expected_headers)).to have_been_made }
+ end
+
+ context 'with job data' do
+ let(:data) { build_data }
+ let(:expected_headers) do
+ { WebHookService::GITLAB_EVENT_HEADER => 'Job Hook' }
+ end
+
+ it { expect(a_request(:post, expected_hook_url).with(headers: expected_headers)).to have_been_made }
+ end
+ end
+end
diff --git a/spec/models/project_services/jenkins_service_spec.rb b/spec/models/project_services/jenkins_service_spec.rb
new file mode 100644
index 00000000000..4663e41736a
--- /dev/null
+++ b/spec/models/project_services/jenkins_service_spec.rb
@@ -0,0 +1,255 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe JenkinsService do
+ let(:project) { create(:project) }
+ let(:jenkins_url) { 'http://jenkins.example.com/' }
+ let(:jenkins_hook_url) { jenkins_url + 'project/my_project' }
+ let(:jenkins_username) { 'u$er name%2520' }
+ let(:jenkins_password) { 'pas$ word' }
+
+ let(:jenkins_params) do
+ {
+ active: true,
+ project: project,
+ properties: {
+ password: jenkins_password,
+ username: jenkins_username,
+ jenkins_url: jenkins_url,
+ project_name: 'my_project'
+ }
+ }
+ end
+
+ let(:jenkins_authorization) { "Basic " + ::Base64.strict_encode64(jenkins_username + ':' + jenkins_password) }
+
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'username validation' do
+ before do
+ @jenkins_service = described_class.create!(
+ active: active,
+ project: project,
+ properties: {
+ jenkins_url: 'http://jenkins.example.com/',
+ password: 'password',
+ username: 'username',
+ project_name: 'my_project'
+ }
+ )
+ end
+
+ subject { @jenkins_service }
+
+ context 'when the service is active' do
+ let(:active) { true }
+
+ context 'when password was not touched' do
+ before do
+ allow(subject).to receive(:password_touched?).and_return(false)
+ end
+
+ it { is_expected.not_to validate_presence_of :username }
+ end
+
+ context 'when password was touched' do
+ before do
+ allow(subject).to receive(:password_touched?).and_return(true)
+ end
+
+ it { is_expected.to validate_presence_of :username }
+ end
+
+ context 'when password is blank' do
+ it 'does not validate the username' do
+ expect(subject).not_to validate_presence_of :username
+
+ subject.password = ''
+ subject.save!
+ end
+ end
+ end
+
+ context 'when the service is inactive' do
+ let(:active) { false }
+
+ it { is_expected.not_to validate_presence_of :username }
+ end
+ end
+
+ describe '#hook_url' do
+ let(:username) { nil }
+ let(:password) { nil }
+ let(:jenkins_service) do
+ described_class.new(
+ project: project,
+ properties: {
+ jenkins_url: jenkins_url,
+ project_name: 'my_project',
+ username: username,
+ password: password
+ }
+ )
+ end
+
+ subject { jenkins_service.hook_url }
+
+ context 'when the jenkins_url has no relative path' do
+ let(:jenkins_url) { 'http://jenkins.example.com/' }
+
+ it { is_expected.to eq('http://jenkins.example.com/project/my_project') }
+ end
+
+ context 'when the jenkins_url has relative path' do
+ let(:jenkins_url) { 'http://organization.example.com/jenkins' }
+
+ it { is_expected.to eq('http://organization.example.com/jenkins/project/my_project') }
+ end
+
+ context 'userinfo is missing and username and password are set' do
+ let(:jenkins_url) { 'http://organization.example.com/jenkins' }
+ let(:username) { 'u$ername' }
+ let(:password) { 'pas$ word' }
+
+ it { is_expected.to eq('http://u%24ername:pas%24%20word@organization.example.com/jenkins/project/my_project') }
+ end
+
+ context 'userinfo is provided and username and password are set' do
+ let(:jenkins_url) { 'http://u:p@organization.example.com/jenkins' }
+ let(:username) { 'username' }
+ let(:password) { 'password' }
+
+ it { is_expected.to eq('http://username:password@organization.example.com/jenkins/project/my_project') }
+ end
+
+ context 'userinfo is provided username and password are not set' do
+ let(:jenkins_url) { 'http://u:p@organization.example.com/jenkins' }
+
+ it { is_expected.to eq('http://u:p@organization.example.com/jenkins/project/my_project') }
+ end
+ end
+
+ describe '#test' do
+ it 'returns the right status' do
+ user = create(:user, username: 'username')
+ project = create(:project, name: 'project')
+ push_sample_data = Gitlab::DataBuilder::Push.build_sample(project, user)
+ jenkins_service = described_class.create!(jenkins_params)
+ stub_request(:post, jenkins_hook_url).with(headers: { 'Authorization' => jenkins_authorization })
+
+ result = jenkins_service.test(push_sample_data)
+
+ expect(result).to eq({ success: true, result: '' })
+ end
+ end
+
+ describe '#execute' do
+ let(:user) { create(:user, username: 'username') }
+ let(:namespace) { create(:group, :private) }
+ let(:project) { create(:project, :private, name: 'project', namespace: namespace) }
+ let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
+ let(:jenkins_service) { described_class.create!(jenkins_params) }
+
+ before do
+ stub_request(:post, jenkins_hook_url)
+ end
+
+ it 'invokes the Jenkins API' do
+ jenkins_service.execute(push_sample_data)
+
+ expect(a_request(:post, jenkins_hook_url)).to have_been_made.once
+ end
+
+ it 'adds default web hook headers to the request' do
+ jenkins_service.execute(push_sample_data)
+
+ expect(
+ a_request(:post, jenkins_hook_url)
+ .with(headers: { 'X-Gitlab-Event' => 'Push Hook', 'Authorization' => jenkins_authorization })
+ ).to have_been_made.once
+ end
+
+ it 'request url contains properly serialized username and password' do
+ jenkins_service.execute(push_sample_data)
+
+ expect(
+ a_request(:post, 'http://jenkins.example.com/project/my_project')
+ .with(headers: { 'Authorization' => jenkins_authorization })
+ ).to have_been_made.once
+ end
+ end
+
+ describe 'Stored password invalidation' do
+ let(:project) { create(:project) }
+
+ context 'when a password was previously set' do
+ before do
+ @jenkins_service = described_class.create!(
+ project: project,
+ properties: {
+ jenkins_url: 'http://jenkins.example.com/',
+ username: 'jenkins',
+ password: 'password'
+ }
+ )
+ end
+
+ it 'resets password if url changed' do
+ @jenkins_service.jenkins_url = 'http://jenkins-edited.example.com/'
+ @jenkins_service.save!
+ expect(@jenkins_service.password).to be_nil
+ end
+
+ it 'resets password if username is blank' do
+ @jenkins_service.username = ''
+ @jenkins_service.save!
+ expect(@jenkins_service.password).to be_nil
+ end
+
+ it 'does not reset password if username changed' do
+ @jenkins_service.username = 'some_name'
+ @jenkins_service.save!
+ expect(@jenkins_service.password).to eq('password')
+ end
+
+ it 'does not reset password if new url is set together with password, even if it\'s the same password' do
+ @jenkins_service.jenkins_url = 'http://jenkins_edited.example.com/'
+ @jenkins_service.password = 'password'
+ @jenkins_service.save!
+ expect(@jenkins_service.password).to eq('password')
+ expect(@jenkins_service.jenkins_url).to eq('http://jenkins_edited.example.com/')
+ end
+
+ it 'resets password if url changed, even if setter called multiple times' do
+ @jenkins_service.jenkins_url = 'http://jenkins1.example.com/'
+ @jenkins_service.jenkins_url = 'http://jenkins1.example.com/'
+ @jenkins_service.save!
+ expect(@jenkins_service.password).to be_nil
+ end
+ end
+
+ context 'when no password was previously set' do
+ before do
+ @jenkins_service = described_class.create!(
+ project: create(:project),
+ properties: {
+ jenkins_url: 'http://jenkins.example.com/',
+ username: 'jenkins'
+ }
+ )
+ end
+
+ it 'saves password if new url is set together with password' do
+ @jenkins_service.jenkins_url = 'http://jenkins_edited.example.com/'
+ @jenkins_service.password = 'password'
+ @jenkins_service.save!
+ expect(@jenkins_service.password).to eq('password')
+ expect(@jenkins_service.jenkins_url).to eq('http://jenkins_edited.example.com/')
+ end
+ end
+ end
+end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 7741fea8717..e7cd3d7f537 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -66,6 +66,19 @@ RSpec.describe JiraService do
end
end
+ describe '#fields' do
+ let(:service) { create(:jira_service) }
+
+ subject(:fields) { service.fields }
+
+ it 'includes transition help link' do
+ transition_id_field = fields.find { |field| field[:name] == 'jira_issue_transition_id' }
+
+ expect(transition_id_field[:title]).to eq('Jira workflow transition IDs')
+ expect(transition_id_field[:help]).to include('/help/user/project/integrations/jira')
+ end
+ end
+
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index c8b96963d5d..a71b0eb842a 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -33,6 +33,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to have_many(:deploy_keys) }
it { is_expected.to have_many(:hooks) }
it { is_expected.to have_many(:protected_branches) }
+ it { is_expected.to have_many(:exported_protected_branches) }
it { is_expected.to have_one(:slack_service) }
it { is_expected.to have_one(:microsoft_teams_service) }
it { is_expected.to have_one(:mattermost_service) }
@@ -146,6 +147,10 @@ RSpec.describe Project, factory_default: :keep do
let(:container_without_wiki) { create(:project) }
end
+ it_behaves_like 'can move repository storage' do
+ let_it_be(:container) { create(:project, :repository) }
+ end
+
it 'has an inverse relationship with merge requests' do
expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project)
end
@@ -607,6 +612,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
it { is_expected.to delegate_method(:root_ancestor).to(:namespace).with_arguments(allow_nil: true) }
it { is_expected.to delegate_method(:last_pipeline).to(:commit).with_arguments(allow_nil: true) }
+ it { is_expected.to delegate_method(:allow_editing_commit_messages?).to(:project_setting) }
end
describe 'reference methods' do
@@ -1534,6 +1540,42 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe '.service_desk_custom_address_enabled?' do
+ let_it_be(:project) { create(:project, service_desk_enabled: true) }
+
+ subject(:address_enabled) { project.service_desk_custom_address_enabled? }
+
+ context 'when service_desk_email is enabled' do
+ before do
+ allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(true)
+ end
+
+ it 'returns true' do
+ expect(address_enabled).to be_truthy
+ end
+
+ context 'when service_desk_custom_address flag is disabled' do
+ before do
+ stub_feature_flags(service_desk_custom_address: false)
+ end
+
+ it 'returns false' do
+ expect(address_enabled).to be_falsey
+ end
+ end
+ end
+
+ context 'when service_desk_email is disabled' do
+ before do
+ allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(false)
+ end
+
+ it 'returns false when service_desk_email is disabled' do
+ expect(address_enabled).to be_falsey
+ end
+ end
+ end
+
describe '.find_by_service_desk_project_key' do
it 'returns the correct project' do
project1 = create(:project)
@@ -3002,39 +3044,6 @@ RSpec.describe Project, factory_default: :keep do
end
end
- describe '#set_repository_read_only!' do
- let(:project) { create(:project) }
-
- it 'makes the repository read-only' do
- expect { project.set_repository_read_only! }
- .to change(project, :repository_read_only?)
- .from(false)
- .to(true)
- end
-
- it 'raises an error if the project is already read-only' do
- project.set_repository_read_only!
-
- expect { project.set_repository_read_only! }.to raise_error(described_class::RepositoryReadOnlyError, /already read-only/)
- end
-
- it 'raises an error when there is an existing git transfer in progress' do
- allow(project).to receive(:git_transfer_in_progress?) { true }
-
- expect { project.set_repository_read_only! }.to raise_error(described_class::RepositoryReadOnlyError, /in progress/)
- end
- end
-
- describe '#set_repository_writable!' do
- it 'sets repository_read_only to false' do
- project = create(:project, :read_only)
-
- expect { project.set_repository_writable! }
- .to change(project, :repository_read_only)
- .from(true).to(false)
- end
- end
-
describe '#pushes_since_gc' do
let(:project) { build_stubbed(:project) }
@@ -4281,29 +4290,33 @@ RSpec.describe Project, factory_default: :keep do
end
describe '#git_transfer_in_progress?' do
+ using RSpec::Parameterized::TableSyntax
+
let(:project) { build(:project) }
subject { project.git_transfer_in_progress? }
- it 'returns false when repo_reference_count and wiki_reference_count are 0' do
- allow(project).to receive(:repo_reference_count) { 0 }
- allow(project).to receive(:wiki_reference_count) { 0 }
-
- expect(subject).to be_falsey
+ where(:project_reference_counter, :wiki_reference_counter, :design_reference_counter, :result) do
+ 0 | 0 | 0 | false
+ 2 | 0 | 0 | true
+ 0 | 2 | 0 | true
+ 0 | 0 | 2 | true
end
- it 'returns true when repo_reference_count is > 0' do
- allow(project).to receive(:repo_reference_count) { 2 }
- allow(project).to receive(:wiki_reference_count) { 0 }
-
- expect(subject).to be_truthy
- end
-
- it 'returns true when wiki_reference_count is > 0' do
- allow(project).to receive(:repo_reference_count) { 0 }
- allow(project).to receive(:wiki_reference_count) { 2 }
+ with_them do
+ before do
+ allow(project).to receive(:reference_counter).with(type: Gitlab::GlRepository::PROJECT) do
+ double(:project_reference_counter, value: project_reference_counter)
+ end
+ allow(project).to receive(:reference_counter).with(type: Gitlab::GlRepository::WIKI) do
+ double(:wiki_reference_counter, value: wiki_reference_counter)
+ end
+ allow(project).to receive(:reference_counter).with(type: Gitlab::GlRepository::DESIGN) do
+ double(:design_reference_counter, value: design_reference_counter)
+ end
+ end
- expect(subject).to be_truthy
+ specify { expect(subject).to be result }
end
end
@@ -4929,6 +4942,7 @@ RSpec.describe Project, factory_default: :keep do
expect(project).to receive(:after_create_default_branch)
expect(project).to receive(:refresh_markdown_cache!)
expect(InternalId).to receive(:flush_records!).with(project: project)
+ expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:repository_size])
expect(DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id)
expect(project).to receive(:write_repository_config)
@@ -5019,11 +5033,11 @@ RSpec.describe Project, factory_default: :keep do
end
end
- describe "#default_branch" do
- context "with an empty repository" do
+ describe '#default_branch' do
+ context 'with an empty repository' do
let_it_be(:project) { create(:project_empty_repo) }
- context "group.default_branch_name is available" do
+ context 'group.default_branch_name is available' do
let(:project_group) { create(:group) }
let(:project) { create(:project, path: 'avatar', namespace: project_group) }
@@ -5036,19 +5050,19 @@ RSpec.describe Project, factory_default: :keep do
.and_return('example_branch')
end
- it "returns the group default value" do
- expect(project.default_branch).to eq("example_branch")
+ it 'returns the group default value' do
+ expect(project.default_branch).to eq('example_branch')
end
end
- context "Gitlab::CurrentSettings.default_branch_name is available" do
+ context 'Gitlab::CurrentSettings.default_branch_name is available' do
before do
expect(Gitlab::CurrentSettings)
.to receive(:default_branch_name)
.and_return(example_branch_name)
end
- context "is missing or nil" do
+ context 'is missing or nil' do
let(:example_branch_name) { nil }
it "returns nil" do
@@ -5056,10 +5070,18 @@ RSpec.describe Project, factory_default: :keep do
end
end
- context "is present" do
- let(:example_branch_name) { "example_branch_name" }
+ context 'is blank' do
+ let(:example_branch_name) { '' }
+
+ it 'returns nil' do
+ expect(project.default_branch).to be_nil
+ end
+ end
- it "returns the expected branch name" do
+ context 'is present' do
+ let(:example_branch_name) { 'example_branch_name' }
+
+ it 'returns the expected branch name' do
expect(project.default_branch).to eq(example_branch_name)
end
end
@@ -5564,6 +5586,26 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe '#disabled_services' do
+ subject { build(:project).disabled_services }
+
+ context 'without datadog_ci_integration' do
+ before do
+ stub_feature_flags(datadog_ci_integration: false)
+ end
+
+ it { is_expected.to include('datadog') }
+ end
+
+ context 'with datadog_ci_integration' do
+ before do
+ stub_feature_flags(datadog_ci_integration: true)
+ end
+
+ it { is_expected.not_to include('datadog') }
+ end
+ end
+
describe '#find_or_initialize_service' do
it 'avoids N+1 database queries' do
allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover])
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 2d283766edb..cb1baa02e96 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -313,17 +313,6 @@ RSpec.describe ProjectStatistics do
it 'stores the size of related uploaded files' do
expect(statistics.update_uploads_size).to eq(3.megabytes)
end
-
- context 'with feature flag disabled' do
- before do
- statistics.update_columns(uploads_size: 0)
- stub_feature_flags(count_uploads_size_in_storage_stats: false)
- end
-
- it 'does not store the size of related uploaded files' do
- expect(statistics.update_uploads_size).to eq(0)
- end
- end
end
describe '#update_storage_size' do
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
index 0aba51ea567..051cb78a6b6 100644
--- a/spec/models/protected_branch/push_access_level_spec.rb
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -34,4 +34,59 @@ RSpec.describe ProtectedBranch::PushAccessLevel do
expect(level.errors.full_messages).to contain_exactly('Deploy key is not enabled for this project')
end
end
+
+ describe '#check_access' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:protected_branch) { create(:protected_branch, :no_one_can_push, project: project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:deploy_key) { create(:deploy_key, user: user) }
+ let!(:deploy_keys_project) { create(:deploy_keys_project, project: project, deploy_key: deploy_key, can_push: can_push) }
+ let(:can_push) { true }
+
+ before_all do
+ project.add_guest(user)
+ end
+
+ context 'when this push_access_level is tied to a deploy key' do
+ let(:push_access_level) { create(:protected_branch_push_access_level, protected_branch: protected_branch, deploy_key: deploy_key) }
+
+ context 'when the deploy key is among the active keys for this project' do
+ specify do
+ expect(push_access_level.check_access(user)).to be_truthy
+ end
+
+ context 'when the deploy_keys_on_protected_branches FF is false' do
+ before do
+ stub_feature_flags(deploy_keys_on_protected_branches: false)
+ end
+
+ it 'is false' do
+ expect(push_access_level.check_access(user)).to be_falsey
+ end
+ end
+ end
+
+ context 'when the deploy key is not among the active keys of this project' do
+ let(:can_push) { false }
+
+ it 'is false' do
+ expect(push_access_level.check_access(user)).to be_falsey
+ end
+ end
+ end
+ end
+
+ describe '#type' do
+ let(:push_level_access) { build(:protected_branch_push_access_level) }
+
+ it 'returns :deploy_key when a deploy key is tied to the protected branch' do
+ push_level_access.deploy_key = create(:deploy_key)
+
+ expect(push_level_access.type).to eq(:deploy_key)
+ end
+
+ it 'returns :role by default' do
+ expect(push_level_access.type).to eq(:role)
+ end
+ end
end
diff --git a/spec/models/raw_usage_data_spec.rb b/spec/models/raw_usage_data_spec.rb
index c10db63da56..7acfb8c19af 100644
--- a/spec/models/raw_usage_data_spec.rb
+++ b/spec/models/raw_usage_data_spec.rb
@@ -16,28 +16,10 @@ RSpec.describe RawUsageData do
describe '#update_sent_at!' do
let(:raw_usage_data) { create(:raw_usage_data) }
- context 'with save_raw_usage_data feature enabled' do
- before do
- stub_feature_flags(save_raw_usage_data: true)
- end
+ it 'updates sent_at' do
+ raw_usage_data.update_sent_at!
- it 'updates sent_at' do
- raw_usage_data.update_sent_at!
-
- expect(raw_usage_data.sent_at).not_to be_nil
- end
- end
-
- context 'with save_raw_usage_data feature disabled' do
- before do
- stub_feature_flags(save_raw_usage_data: false)
- end
-
- it 'updates sent_at' do
- raw_usage_data.update_sent_at!
-
- expect(raw_usage_data.sent_at).to be_nil
- end
+ expect(raw_usage_data.sent_at).not_to be_nil
end
end
end
diff --git a/spec/models/release_highlight_spec.rb b/spec/models/release_highlight_spec.rb
new file mode 100644
index 00000000000..ac252e6d6cf
--- /dev/null
+++ b/spec/models/release_highlight_spec.rb
@@ -0,0 +1,181 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ReleaseHighlight do
+ let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
+
+ before do
+ allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
+ end
+
+ after do
+ ReleaseHighlight.instance_variable_set(:@file_paths, nil)
+ end
+
+ describe '.for_version' do
+ subject { ReleaseHighlight.for_version(version: version) }
+
+ let(:version) { '1.1' }
+
+ context 'with version param that exists' do
+ it 'returns items from that version' do
+ expect(subject.items.first['title']).to eq("It's gonna be a bright")
+ end
+ end
+
+ context 'with version param that does NOT exist' do
+ let(:version) { '84.0' }
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+
+ describe '.paginated' do
+ let(:dot_com) { false }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(dot_com)
+ end
+
+ context 'with page param' do
+ subject { ReleaseHighlight.paginated(page: page) }
+
+ context 'when there is another page of results' do
+ let(:page) { 2 }
+
+ it 'responds with paginated results' do
+ expect(subject[:items].first['title']).to eq('bright')
+ expect(subject[:next_page]).to eq(3)
+ end
+ end
+
+ context 'when there is NOT another page of results' do
+ let(:page) { 3 }
+
+ it 'responds with paginated results and no next_page' do
+ expect(subject[:items].first['title']).to eq("It's gonna be a bright")
+ expect(subject[:next_page]).to eq(nil)
+ end
+ end
+
+ context 'when that specific page does not exist' do
+ let(:page) { 84 }
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+
+ context 'with no page param' do
+ subject { ReleaseHighlight.paginated }
+
+ it 'uses multiple levels of cache' do
+ expect(Rails.cache).to receive(:fetch).with("release_highlight:items:page-1:#{Gitlab.revision}", { expires_in: described_class::CACHE_DURATION }).and_call_original
+ expect(Rails.cache).to receive(:fetch).with("release_highlight:file_paths:#{Gitlab.revision}", { expires_in: described_class::CACHE_DURATION }).and_call_original
+
+ subject
+ end
+
+ it 'returns platform specific items' do
+ expect(subject[:items].count).to eq(1)
+ expect(subject[:items].first['title']).to eq("bright and sunshinin' day")
+ expect(subject[:next_page]).to eq(2)
+ end
+
+ it 'parses the body as markdown and returns html' do
+ expect(subject[:items].first['body']).to match("<h2 id=\"bright-and-sunshinin-day\">bright and sunshinin’ day</h2>")
+ end
+
+ it 'logs an error if theres an error parsing markdown for an item, and skips it' do
+ allow(Kramdown::Document).to receive(:new).and_raise
+
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ expect(subject[:items]).to be_empty
+ end
+
+ context 'when Gitlab.com' do
+ let(:dot_com) { true }
+
+ it 'responds with a different set of data' do
+ expect(subject[:items].count).to eq(1)
+ expect(subject[:items].first['title']).to eq("I think I can make it now the pain is gone")
+ end
+ end
+
+ context 'YAML parsing throws an exception' do
+ it 'fails gracefully and logs an error' do
+ allow(YAML).to receive(:safe_load).and_raise(Psych::Exception)
+
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ expect(subject).to be_nil
+ end
+ end
+ end
+ end
+
+ describe '.most_recent_item_count' do
+ subject { ReleaseHighlight.most_recent_item_count }
+
+ it 'uses process memory cache' do
+ expect(Gitlab::ProcessMemoryCache.cache_backend).to receive(:fetch).with("release_highlight:recent_item_count:#{Gitlab.revision}", expires_in: described_class::CACHE_DURATION)
+
+ subject
+ end
+
+ context 'when recent release items exist' do
+ it 'returns the count from the most recent file' do
+ allow(ReleaseHighlight).to receive(:paginated).and_return(double(:paginated, items: [double(:item)]))
+
+ expect(subject).to eq(1)
+ end
+ end
+
+ context 'when recent release items do NOT exist' do
+ it 'returns nil' do
+ allow(ReleaseHighlight).to receive(:paginated).and_return(nil)
+
+ expect(subject).to be_nil
+ end
+ end
+ end
+
+ describe '.versions' do
+ subject { described_class.versions }
+
+ it 'uses process memory cache' do
+ expect(Gitlab::ProcessMemoryCache.cache_backend).to receive(:fetch).with("release_highlight:versions:#{Gitlab.revision}", { expires_in: described_class::CACHE_DURATION })
+
+ subject
+ end
+
+ it 'returns versions from the file paths' do
+ expect(subject).to eq(['1.5', '1.2', '1.1'])
+ end
+
+ context 'when there are more than 12 versions' do
+ let(:file_paths) do
+ i = 0
+ Array.new(20) { "20201225_01_#{i += 1}.yml" }
+ end
+
+ it 'limits to 12 versions' do
+ allow(ReleaseHighlight).to receive(:file_paths).and_return(file_paths)
+ expect(subject.count).to eq(12)
+ end
+ end
+ end
+
+ describe 'QueryResult' do
+ subject { ReleaseHighlight::QueryResult.new(items: items, next_page: 2) }
+
+ let(:items) { [:item] }
+
+ it 'responds to map' do
+ expect(subject.map(&:to_s)).to eq(items.map(&:to_s))
+ end
+ end
+end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 31211f8ff2c..c1f073e26d1 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2335,7 +2335,7 @@ RSpec.describe Repository do
end
it 'caches the response' do
- expect(repository).to receive(:readme).and_call_original.once
+ expect(repository.head_tree).to receive(:readme_path).and_call_original.once
2.times do
expect(repository.readme_path).to eq("README.md")
diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb
index da1fe70c891..44de83d9240 100644
--- a/spec/models/resource_label_event_spec.rb
+++ b/spec/models/resource_label_event_spec.rb
@@ -51,14 +51,6 @@ RSpec.describe ResourceLabelEvent, type: :model do
end
context 'callbacks' do
- describe '#usage_metrics' do
- it 'tracks changed labels' do
- expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_label_changed_action)
-
- subject.save!
- end
- end
-
describe '#expire_etag_cache' do
def expect_expiration(issue)
expect_next_instance_of(Gitlab::EtagCaching::Store) do |instance|
diff --git a/spec/models/resource_state_event_spec.rb b/spec/models/resource_state_event_spec.rb
index b8a93bdbe3b..2b4898b750a 100644
--- a/spec/models/resource_state_event_spec.rb
+++ b/spec/models/resource_state_event_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe ResourceStateEvent, type: :model do
end
context 'callbacks' do
- describe '#usage_metrics' do
+ describe '#issue_usage_metrics' do
it 'tracks closed issues' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_closed_action)
@@ -53,6 +53,12 @@ RSpec.describe ResourceStateEvent, type: :model do
create(described_class.name.underscore.to_sym, issue: issue, state: described_class.states[:reopened])
end
+
+ it 'does not track merge requests' do
+ expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:track_issue_closed_action)
+
+ create(described_class.name.underscore.to_sym, merge_request: merge_request, state: described_class.states[:closed])
+ end
end
end
end
diff --git a/spec/models/sentry_issue_spec.rb b/spec/models/sentry_issue_spec.rb
index 33654bf5e1a..c24350d7067 100644
--- a/spec/models/sentry_issue_spec.rb
+++ b/spec/models/sentry_issue_spec.rb
@@ -27,6 +27,12 @@ RSpec.describe SentryIssue do
expect(duplicate_sentry_issue).to be_invalid
expect(duplicate_sentry_issue.errors.full_messages.first).to include('is already associated')
end
+
+ describe 'when importing' do
+ subject { create(:sentry_issue, importing: true) }
+
+ it { is_expected.not_to validate_presence_of(:issue) }
+ end
end
describe 'callbacks' do
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 402c1a3d19b..04b3920cd6c 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -245,7 +245,7 @@ RSpec.describe Service do
context 'with a previous existing service (MockCiService) and a new service (Asana)' do
before do
- Service.insert(type: 'MockCiService', instance: true)
+ Service.insert({ type: 'MockCiService', instance: true })
Service.delete_by(type: 'AsanaService', instance: true)
end
@@ -291,7 +291,7 @@ RSpec.describe Service do
context 'with a previous existing service (Previous) and a new service (Asana)' do
before do
- Service.insert(type: 'PreviousService', template: true)
+ Service.insert({ type: 'PreviousService', template: true })
Service.delete_by(type: 'AsanaService', template: true)
end
@@ -916,5 +916,14 @@ RSpec.describe Service do
described_class.available_services_names(include_dev: false)
end
+
+ it { expect(described_class.available_services_names).to include('jenkins') }
+ end
+
+ describe '.project_specific_services_names' do
+ it do
+ expect(described_class.project_specific_services_names)
+ .to include(*described_class::PROJECT_SPECIFIC_SERVICE_NAMES)
+ end
end
end
diff --git a/spec/models/snippet_repository_storage_move_spec.rb b/spec/models/snippet_repository_storage_move_spec.rb
new file mode 100644
index 00000000000..c9feff0c22f
--- /dev/null
+++ b/spec/models/snippet_repository_storage_move_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe SnippetRepositoryStorageMove, type: :model do
+ it_behaves_like 'handles repository moves' do
+ let_it_be_with_refind(:container) { create(:snippet) }
+
+ let(:repository_storage_factory_key) { :snippet_repository_storage_move }
+ let(:error_key) { :snippet }
+ let(:repository_storage_worker) { nil } # TODO set to SnippetUpdateRepositoryStorageWorker after https://gitlab.com/gitlab-org/gitlab/-/issues/218991 is implemented
+ end
+end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index d74f5faab7f..f87259ea048 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -21,6 +21,7 @@ RSpec.describe Snippet do
it { is_expected.to have_many(:user_mentions).class_name("SnippetUserMention") }
it { is_expected.to have_one(:snippet_repository) }
it { is_expected.to have_one(:statistics).class_name('SnippetStatistics').dependent(:destroy) }
+ it { is_expected.to have_many(:repository_storage_moves).class_name('SnippetRepositoryStorageMove').inverse_of(:container) }
end
describe 'validation' do
@@ -481,17 +482,9 @@ RSpec.describe Snippet do
end
describe '#blobs' do
- let(:snippet) { create(:snippet) }
-
- it 'returns a blob representing the snippet data' do
- blob = snippet.blob
-
- expect(blob).to be_a(Blob)
- expect(blob.path).to eq(snippet.file_name)
- expect(blob.data).to eq(snippet.content)
- end
-
context 'when repository does not exist' do
+ let(:snippet) { create(:snippet) }
+
it 'returns empty array' do
expect(snippet.blobs).to be_empty
end
@@ -777,4 +770,30 @@ RSpec.describe Snippet do
it { is_expected.to be_falsey }
end
end
+
+ describe '#git_transfer_in_progress?' do
+ let(:snippet) { build(:snippet) }
+
+ subject { snippet.git_transfer_in_progress? }
+
+ it 'returns true when there are git transfers' do
+ allow(snippet).to receive(:reference_counter).with(type: Gitlab::GlRepository::SNIPPET) do
+ double(:reference_counter, value: 2)
+ end
+
+ expect(subject).to eq true
+ end
+
+ it 'returns false when there are not git transfers' do
+ allow(snippet).to receive(:reference_counter).with(type: Gitlab::GlRepository::SNIPPET) do
+ double(:reference_counter, value: 0)
+ end
+
+ expect(subject).to eq false
+ end
+ end
+
+ it_behaves_like 'can move repository storage' do
+ let_it_be(:container) { create(:snippet, :repository) }
+ end
end
diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb
index e88fc13ecee..9a7624c253a 100644
--- a/spec/models/suggestion_spec.rb
+++ b/spec/models/suggestion_spec.rb
@@ -12,6 +12,12 @@ RSpec.describe Suggestion do
describe 'validations' do
it { is_expected.to validate_presence_of(:note) }
+ context 'when importing' do
+ subject { create(:suggestion, importing: true) }
+
+ it { is_expected.not_to validate_presence_of(:note) }
+ end
+
context 'when suggestion is applied' do
before do
allow(subject).to receive(:applied?).and_return(true)
diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb
index 9a6b57afb97..144c65d2f62 100644
--- a/spec/models/system_note_metadata_spec.rb
+++ b/spec/models/system_note_metadata_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe SystemNoteMetadata do
context 'when action type is invalid' do
subject do
- build(:system_note_metadata, note: build(:note), action: 'invalid_type' )
+ build(:system_note_metadata, note: build(:note), action: 'invalid_type')
end
it { is_expected.to be_invalid }
@@ -21,7 +21,15 @@ RSpec.describe SystemNoteMetadata do
context 'when action type is valid' do
subject do
- build(:system_note_metadata, note: build(:note), action: 'merge' )
+ build(:system_note_metadata, note: build(:note), action: 'merge')
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when importing' do
+ subject do
+ build(:system_note_metadata, note: nil, action: 'merge', importing: true)
end
it { is_expected.to be_valid }
diff --git a/spec/models/terraform/state_spec.rb b/spec/models/terraform/state_spec.rb
index ca8fe4cca3b..ef91e4a5a71 100644
--- a/spec/models/terraform/state_spec.rb
+++ b/spec/models/terraform/state_spec.rb
@@ -3,21 +3,13 @@
require 'spec_helper'
RSpec.describe Terraform::State do
- subject { create(:terraform_state, :with_file) }
-
- let(:terraform_state_file) { fixture_file('terraform/terraform.tfstate') }
-
- it { is_expected.to be_a FileStoreMounter }
+ subject { create(:terraform_state, :with_version) }
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:locked_by_user).class_name('User') }
it { is_expected.to validate_presence_of(:project_id) }
- before do
- stub_terraform_state_object_storage
- end
-
describe 'scopes' do
describe '.ordered_by_name' do
let_it_be(:project) { create(:project) }
@@ -35,64 +27,18 @@ RSpec.describe Terraform::State do
end
end
- describe '#file' do
- context 'when a file exists' do
- it 'does not use the default file' do
- expect(subject.file.read).to eq(terraform_state_file)
- end
- end
- end
-
- describe '#file_store' do
- context 'when a value is set' do
- it 'returns the value' do
- [ObjectStorage::Store::LOCAL, ObjectStorage::Store::REMOTE].each do |store|
- expect(build(:terraform_state, file_store: store).file_store).to eq(store)
- end
- end
- end
- end
-
- describe '#update_file_store' do
- context 'when file is stored in object storage' do
- it_behaves_like 'mounted file in object store'
- end
-
- context 'when file is stored locally' do
- before do
- stub_terraform_state_object_storage(enabled: false)
- end
-
- it_behaves_like 'mounted file in local store'
- end
- end
-
describe '#latest_file' do
- subject { terraform_state.latest_file }
-
- context 'versioning is enabled' do
- let(:terraform_state) { create(:terraform_state, :with_version) }
- let(:latest_version) { terraform_state.latest_version }
-
- it { is_expected.to eq latest_version.file }
-
- context 'but no version exists yet' do
- let(:terraform_state) { create(:terraform_state) }
+ let(:terraform_state) { create(:terraform_state, :with_version) }
+ let(:latest_version) { terraform_state.latest_version }
- it { is_expected.to be_nil }
- end
- end
-
- context 'versioning is disabled' do
- let(:terraform_state) { create(:terraform_state, :with_file) }
+ subject { terraform_state.latest_file }
- it { is_expected.to eq terraform_state.file }
+ it { is_expected.to eq latest_version.file }
- context 'and a version exists (migration to versioned in progress)' do
- let!(:migrated_version) { create(:terraform_state_version, terraform_state: terraform_state) }
+ context 'but no version exists yet' do
+ let(:terraform_state) { create(:terraform_state) }
- it { is_expected.to eq terraform_state.latest_version.file }
- end
+ it { is_expected.to be_nil }
end
end
@@ -115,39 +61,30 @@ RSpec.describe Terraform::State do
end
end
- context 'versioning is disabled' do
- let(:terraform_state) { create(:terraform_state, :with_file) }
-
- it 'modifies the existing state record' do
- expect { subject }.not_to change { Terraform::StateVersion.count }
+ context 'versioning is disabled (migration to versioned in progress)' do
+ let(:terraform_state) { create(:terraform_state, versioning_enabled: false) }
+ let!(:migrated_version) { create(:terraform_state_version, terraform_state: terraform_state, version: 0) }
- expect(terraform_state.latest_file.read).to eq(data)
- end
-
- context 'and a version exists (migration to versioned in progress)' do
- let!(:migrated_version) { create(:terraform_state_version, terraform_state: terraform_state, version: 0) }
+ it 'creates a new version, corrects the migrated version number, and marks the state as versioned' do
+ expect { subject }.to change { Terraform::StateVersion.count }
- it 'creates a new version, corrects the migrated version number, and marks the state as versioned' do
- expect { subject }.to change { Terraform::StateVersion.count }
+ expect(migrated_version.reload.version).to eq(1)
+ expect(migrated_version.file.read).to eq(fixture_file('terraform/terraform.tfstate'))
- expect(migrated_version.reload.version).to eq(1)
- expect(migrated_version.file.read).to eq(terraform_state_file)
+ expect(terraform_state.reload.latest_version.version).to eq(version)
+ expect(terraform_state.latest_version.file.read).to eq(data)
+ expect(terraform_state).to be_versioning_enabled
+ end
- expect(terraform_state.reload.latest_version.version).to eq(version)
- expect(terraform_state.latest_version.file.read).to eq(data)
- expect(terraform_state).to be_versioning_enabled
+ context 'the current version cannot be determined' do
+ before do
+ migrated_version.update!(file: CarrierWaveStringFile.new('invalid-json'))
end
- context 'the current version cannot be determined' do
- before do
- migrated_version.update!(file: CarrierWaveStringFile.new('invalid-json'))
- end
-
- it 'uses version - 1 to correct the migrated version number' do
- expect { subject }.to change { Terraform::StateVersion.count }
+ it 'uses version - 1 to correct the migrated version number' do
+ expect { subject }.to change { Terraform::StateVersion.count }
- expect(migrated_version.reload.version).to eq(2)
- end
+ expect(migrated_version.reload.version).to eq(2)
end
end
end
diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb
index ae1697fb7e6..e9019b55635 100644
--- a/spec/models/timelog_spec.rb
+++ b/spec/models/timelog_spec.rb
@@ -40,6 +40,14 @@ RSpec.describe Timelog do
expect(subject).to be_valid
end
+
+ describe 'when importing' do
+ it 'is valid if issue_id and merge_request_id are missing' do
+ subject.attributes = { issue: nil, merge_request: nil, importing: true }
+
+ expect(subject).to be_valid
+ end
+ end
end
describe 'scopes' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 19a6a3ce3c4..fb05c9e8052 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -99,6 +99,8 @@ RSpec.describe User do
it { is_expected.to have_many(:releases).dependent(:nullify) }
it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:user) }
it { is_expected.to have_many(:reviews).inverse_of(:author) }
+ it { is_expected.to have_many(:merge_request_assignees).inverse_of(:assignee) }
+ it { is_expected.to have_many(:merge_request_reviewers).inverse_of(:reviewer) }
describe "#user_detail" do
it 'does not persist `user_detail` by default' do
@@ -1085,7 +1087,7 @@ RSpec.describe User do
@user.update!(email: 'new_primary@example.com')
end
- it 'adds old primary to secondary emails when secondary is a new email ' do
+ it 'adds old primary to secondary emails when secondary is a new email' do
@user.update!(email: 'new_primary@example.com')
@user.reload
@@ -1521,6 +1523,16 @@ RSpec.describe User do
expect(feed_token).not_to be_blank
expect(user.reload.feed_token).to eq feed_token
end
+
+ it 'ensures no feed token when disabled' do
+ allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true)
+
+ user = create(:user, feed_token: nil)
+ feed_token = user.feed_token
+
+ expect(feed_token).to be_blank
+ expect(user.reload.feed_token).to be_blank
+ end
end
describe 'static object token' do
@@ -1573,6 +1585,80 @@ RSpec.describe User do
end
end
+ describe '#two_factor_otp_enabled?' do
+ let_it_be(:user) { create(:user) }
+
+ context 'when 2FA is enabled by an MFA Device' do
+ let(:user) { create(:user, :two_factor) }
+
+ it { expect(user.two_factor_otp_enabled?).to eq(true) }
+ end
+
+ context 'FortiAuthenticator' do
+ context 'when enabled via GitLab settings' do
+ before do
+ allow(::Gitlab.config.forti_authenticator).to receive(:enabled).and_return(true)
+ end
+
+ context 'when feature is disabled for the user' do
+ before do
+ stub_feature_flags(forti_authenticator: false)
+ end
+
+ it { expect(user.two_factor_otp_enabled?).to eq(false) }
+ end
+
+ context 'when feature is enabled for the user' do
+ before do
+ stub_feature_flags(forti_authenticator: user)
+ end
+
+ it { expect(user.two_factor_otp_enabled?).to eq(true) }
+ end
+ end
+
+ context 'when disabled via GitLab settings' do
+ before do
+ allow(::Gitlab.config.forti_authenticator).to receive(:enabled).and_return(false)
+ end
+
+ it { expect(user.two_factor_otp_enabled?).to eq(false) }
+ end
+ end
+
+ context 'FortiTokenCloud' do
+ context 'when enabled via GitLab settings' do
+ before do
+ allow(::Gitlab.config.forti_token_cloud).to receive(:enabled).and_return(true)
+ end
+
+ context 'when feature is disabled for the user' do
+ before do
+ stub_feature_flags(forti_token_cloud: false)
+ end
+
+ it { expect(user.two_factor_otp_enabled?).to eq(false) }
+ end
+
+ context 'when feature is enabled for the user' do
+ before do
+ stub_feature_flags(forti_token_cloud: user)
+ end
+
+ it { expect(user.two_factor_otp_enabled?).to eq(true) }
+ end
+ end
+
+ context 'when disabled via GitLab settings' do
+ before do
+ allow(::Gitlab.config.forti_token_cloud).to receive(:enabled).and_return(false)
+ end
+
+ it { expect(user.two_factor_otp_enabled?).to eq(false) }
+ end
+ end
+ end
+
describe 'projects' do
before do
@user = create(:user)
@@ -2024,9 +2110,10 @@ RSpec.describe User do
end
describe '.search' do
- let!(:user) { create(:user, name: 'user', username: 'usern', email: 'email@gmail.com') }
- let!(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@gmail.com') }
- let!(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@gmail.com') }
+ let_it_be(:user) { create(:user, name: 'user', username: 'usern', email: 'email@example.com') }
+ let_it_be(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@example.com') }
+ let_it_be(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@example.com') }
+ let_it_be(:email) { create(:email, user: user, email: 'alias@example.com') }
describe 'name matching' do
it 'returns users with a matching name with exact match first' do
@@ -2056,7 +2143,7 @@ RSpec.describe User do
end
it 'does not return users with a partially matching Email' do
- expect(described_class.search(user.email[0..2])).not_to include(user, user2)
+ expect(described_class.search(user.email[1...-1])).to be_empty
end
it 'returns users with a matching Email regardless of the casing' do
@@ -2064,6 +2151,20 @@ RSpec.describe User do
end
end
+ describe 'secondary email matching' do
+ it 'returns users with a matching secondary email' do
+ expect(described_class.search(email.email)).to include(email.user)
+ end
+
+ it 'does not return users with a matching part of secondary email' do
+ expect(described_class.search(email.email[1...-1])).to be_empty
+ end
+
+ it 'returns users with a matching secondary email regardless of the casing' do
+ expect(described_class.search(email.email.upcase)).to include(email.user)
+ end
+ end
+
describe 'username matching' do
it 'returns users with a matching username' do
expect(described_class.search(user.username)).to eq([user, user2])
@@ -2103,65 +2204,119 @@ RSpec.describe User do
end
end
- describe '.search_with_secondary_emails' do
- delegate :search_with_secondary_emails, to: :described_class
+ describe '.search_without_secondary_emails' do
+ let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) }
+ let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) }
+ let_it_be(:email) { create(:email, user: another_user, email: 'alias@example.com') }
+
+ it 'returns users with a matching name' do
+ expect(described_class.search_without_secondary_emails(user.name)).to eq([user])
+ end
+
+ it 'returns users with a partially matching name' do
+ expect(described_class.search_without_secondary_emails(user.name[0..2])).to eq([user])
+ end
+
+ it 'returns users with a matching name regardless of the casing' do
+ expect(described_class.search_without_secondary_emails(user.name.upcase)).to eq([user])
+ end
+
+ it 'returns users with a matching email' do
+ expect(described_class.search_without_secondary_emails(user.email)).to eq([user])
+ end
- let!(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'john.doe@example.com' ) }
- let!(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'albert.smith@example.com' ) }
- let!(:email) do
- create(:email, user: another_user, email: 'alias@example.com')
+ it 'does not return users with a partially matching email' do
+ expect(described_class.search_without_secondary_emails(user.email[1...-1])).to be_empty
+ end
+
+ it 'returns users with a matching email regardless of the casing' do
+ expect(described_class.search_without_secondary_emails(user.email.upcase)).to eq([user])
+ end
+
+ it 'returns users with a matching username' do
+ expect(described_class.search_without_secondary_emails(user.username)).to eq([user])
+ end
+
+ it 'returns users with a partially matching username' do
+ expect(described_class.search_without_secondary_emails(user.username[0..2])).to eq([user])
+ end
+
+ it 'returns users with a matching username regardless of the casing' do
+ expect(described_class.search_without_secondary_emails(user.username.upcase)).to eq([user])
+ end
+
+ it 'does not return users with a matching whole secondary email' do
+ expect(described_class.search_without_secondary_emails(email.email)).not_to include(email.user)
+ end
+
+ it 'does not return users with a matching part of secondary email' do
+ expect(described_class.search_without_secondary_emails(email.email[1...-1])).to be_empty
end
+ it 'returns no matches for an empty string' do
+ expect(described_class.search_without_secondary_emails('')).to be_empty
+ end
+
+ it 'returns no matches for nil' do
+ expect(described_class.search_without_secondary_emails(nil)).to be_empty
+ end
+ end
+
+ describe '.search_with_secondary_emails' do
+ let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) }
+ let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) }
+ let_it_be(:email) { create(:email, user: another_user, email: 'alias@example.com') }
+
it 'returns users with a matching name' do
- expect(search_with_secondary_emails(user.name)).to eq([user])
+ expect(described_class.search_with_secondary_emails(user.name)).to eq([user])
end
it 'returns users with a partially matching name' do
- expect(search_with_secondary_emails(user.name[0..2])).to eq([user])
+ expect(described_class.search_with_secondary_emails(user.name[0..2])).to eq([user])
end
it 'returns users with a matching name regardless of the casing' do
- expect(search_with_secondary_emails(user.name.upcase)).to eq([user])
+ expect(described_class.search_with_secondary_emails(user.name.upcase)).to eq([user])
end
it 'returns users with a matching email' do
- expect(search_with_secondary_emails(user.email)).to eq([user])
+ expect(described_class.search_with_secondary_emails(user.email)).to eq([user])
end
it 'does not return users with a partially matching email' do
- expect(search_with_secondary_emails(user.email[0..2])).not_to include([user])
+ expect(described_class.search_with_secondary_emails(user.email[1...-1])).to be_empty
end
it 'returns users with a matching email regardless of the casing' do
- expect(search_with_secondary_emails(user.email.upcase)).to eq([user])
+ expect(described_class.search_with_secondary_emails(user.email.upcase)).to eq([user])
end
it 'returns users with a matching username' do
- expect(search_with_secondary_emails(user.username)).to eq([user])
+ expect(described_class.search_with_secondary_emails(user.username)).to eq([user])
end
it 'returns users with a partially matching username' do
- expect(search_with_secondary_emails(user.username[0..2])).to eq([user])
+ expect(described_class.search_with_secondary_emails(user.username[0..2])).to eq([user])
end
it 'returns users with a matching username regardless of the casing' do
- expect(search_with_secondary_emails(user.username.upcase)).to eq([user])
+ expect(described_class.search_with_secondary_emails(user.username.upcase)).to eq([user])
end
it 'returns users with a matching whole secondary email' do
- expect(search_with_secondary_emails(email.email)).to eq([email.user])
+ expect(described_class.search_with_secondary_emails(email.email)).to eq([email.user])
end
it 'does not return users with a matching part of secondary email' do
- expect(search_with_secondary_emails(email.email[1..4])).not_to include([email.user])
+ expect(described_class.search_with_secondary_emails(email.email[1...-1])).to be_empty
end
it 'returns no matches for an empty string' do
- expect(search_with_secondary_emails('')).to be_empty
+ expect(described_class.search_with_secondary_emails('')).to be_empty
end
it 'returns no matches for nil' do
- expect(search_with_secondary_emails(nil)).to be_empty
+ expect(described_class.search_with_secondary_emails(nil)).to be_empty
end
end
@@ -2454,6 +2609,28 @@ RSpec.describe User do
end
end
+ context 'crowd synchronized user' do
+ describe '#crowd_user?' do
+ it 'is true if provider is crowd' do
+ user = create(:omniauth_user, provider: 'crowd')
+
+ expect(user.crowd_user?).to be_truthy
+ end
+
+ it 'is false for other providers' do
+ user = create(:omniauth_user, provider: 'other-provider')
+
+ expect(user.crowd_user?).to be_falsey
+ end
+
+ it 'is false if no extern_uid is provided' do
+ user = create(:omniauth_user, extern_uid: nil)
+
+ expect(user.crowd_user?).to be_falsey
+ end
+ end
+ end
+
describe '#requires_ldap_check?' do
let(:user) { described_class.new }
@@ -2878,6 +3055,57 @@ RSpec.describe User do
end
end
+ describe '#solo_owned_groups' do
+ let_it_be_with_refind(:user) { create(:user) }
+
+ subject(:solo_owned_groups) { user.solo_owned_groups }
+
+ context 'no owned groups' do
+ it { is_expected.to be_empty }
+ end
+
+ context 'has owned groups' do
+ let_it_be(:group) { create(:group) }
+
+ before do
+ group.add_owner(user)
+ end
+
+ context 'not solo owner' do
+ let_it_be(:user2) { create(:user) }
+
+ before do
+ group.add_owner(user2)
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'solo owner' do
+ it { is_expected.to include(group) }
+
+ it 'avoids N+1 queries' do
+ fresh_user = User.find(user.id)
+ control_count = ActiveRecord::QueryRecorder.new do
+ fresh_user.solo_owned_groups
+ end.count
+
+ create(:group).add_owner(user)
+
+ expect { solo_owned_groups }.not_to exceed_query_limit(control_count)
+ end
+ end
+ end
+ end
+
+ describe '#can_remove_self?' do
+ let(:user) { create(:user) }
+
+ it 'returns true' do
+ expect(user.can_remove_self?).to eq true
+ end
+ end
+
describe "#recent_push" do
let(:user) { build(:user) }
let(:project) { build(:project) }
diff --git a/spec/models/wiki_page/meta_spec.rb b/spec/models/wiki_page/meta_spec.rb
index aaac72cbc68..24906d4fb79 100644
--- a/spec/models/wiki_page/meta_spec.rb
+++ b/spec/models/wiki_page/meta_spec.rb
@@ -25,13 +25,8 @@ RSpec.describe WikiPage::Meta do
end
it { is_expected.to validate_presence_of(:project_id) }
- it { is_expected.to validate_presence_of(:title) }
-
- it 'is forbidden to add extremely long titles' do
- expect do
- create(:wiki_page_meta, project: project, title: FFaker::Lorem.characters(300))
- end.to raise_error(ActiveRecord::ValueTooLong)
- end
+ it { is_expected.to validate_length_of(:title).is_at_most(255) }
+ it { is_expected.not_to allow_value(nil).for(:title) }
it 'is forbidden to have two records for the same project with the same canonical_slug' do
the_slug = generate(:sluggified_title)
diff --git a/spec/models/wiki_page/slug_spec.rb b/spec/models/wiki_page/slug_spec.rb
index cf256c67277..9e83b9a8182 100644
--- a/spec/models/wiki_page/slug_spec.rb
+++ b/spec/models/wiki_page/slug_spec.rb
@@ -48,8 +48,9 @@ RSpec.describe WikiPage::Slug do
build(:wiki_page_slug, canonical: canonical, wiki_page_meta: meta)
end
- it { is_expected.to validate_presence_of(:slug) }
it { is_expected.to validate_uniqueness_of(:slug).scoped_to(:wiki_page_meta_id) }
+ it { is_expected.to validate_length_of(:slug).is_at_most(2048) }
+ it { is_expected.not_to allow_value(nil).for(:slug) }
describe 'only_one_slug_can_be_canonical_per_meta_record' do
context 'there are no other slugs' do
diff --git a/spec/models/zoom_meeting_spec.rb b/spec/models/zoom_meeting_spec.rb
index 00a0f92e848..2b45533035d 100644
--- a/spec/models/zoom_meeting_spec.rb
+++ b/spec/models/zoom_meeting_spec.rb
@@ -12,8 +12,8 @@ RSpec.describe ZoomMeeting do
end
describe 'Associations' do
- it { is_expected.to belong_to(:project).required }
- it { is_expected.to belong_to(:issue).required }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:issue) }
end
describe 'scopes' do
@@ -40,6 +40,16 @@ RSpec.describe ZoomMeeting do
end
describe 'Validations' do
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:issue) }
+
+ describe 'when importing' do
+ subject { build(:zoom_meeting, importing: true) }
+
+ it { is_expected.not_to validate_presence_of(:project) }
+ it { is_expected.not_to validate_presence_of(:issue) }
+ end
+
describe 'url' do
it { is_expected.to validate_presence_of(:url) }
it { is_expected.to validate_length_of(:url).is_at_most(255) }
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index 2f9376f9b0a..e677f5558fd 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe GlobalPolicy do
let_it_be(:project_bot) { create(:user, :project_bot) }
let_it_be(:migration_bot) { create(:user, :migration_bot) }
+ let_it_be(:security_bot) { create(:user, :security_bot) }
+
let(:current_user) { create(:user) }
let(:user) { create(:user) }
@@ -148,6 +150,24 @@ RSpec.describe GlobalPolicy do
end
end
+ describe 'rejecting users' do
+ context 'regular user' do
+ it { is_expected.not_to be_allowed(:reject_user) }
+ end
+
+ context 'admin' do
+ let(:current_user) { create(:admin) }
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed(:reject_user) }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.to be_disallowed(:reject_user) }
+ end
+ end
+ end
+
describe 'using project statistics filters' do
context 'regular user' do
it { is_expected.not_to be_allowed(:use_project_statistics_filters) }
@@ -205,6 +225,12 @@ RSpec.describe GlobalPolicy do
it { is_expected.not_to be_allowed(:access_api) }
end
+ context 'security bot' do
+ let(:current_user) { security_bot }
+
+ it { is_expected.not_to be_allowed(:access_api) }
+ end
+
context 'user blocked pending approval' do
before do
current_user.block_pending_approval
@@ -335,6 +361,12 @@ RSpec.describe GlobalPolicy do
it { is_expected.to be_allowed(:access_git) }
end
+ context 'security bot' do
+ let(:current_user) { security_bot }
+
+ it { is_expected.to be_allowed(:access_git) }
+ end
+
describe 'deactivated user' do
before do
current_user.deactivate
@@ -495,6 +527,12 @@ RSpec.describe GlobalPolicy do
it { is_expected.not_to be_allowed(:log_in) }
end
+ context 'security bot' do
+ let(:current_user) { security_bot }
+
+ it { is_expected.not_to be_allowed(:log_in) }
+ end
+
context 'user blocked pending approval' do
before do
current_user.block_pending_approval
diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb
index 8f71cf114c3..514d7303ad7 100644
--- a/spec/policies/namespace_policy_spec.rb
+++ b/spec/policies/namespace_policy_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe NamespacePolicy do
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, owner: owner) }
- let(:owner_permissions) { [:create_projects, :admin_namespace, :read_namespace, :read_statistics, :transfer_projects] }
+ let(:owner_permissions) { [:owner_access, :create_projects, :admin_namespace, :read_namespace, :read_statistics, :transfer_projects] }
subject { described_class.new(current_user, namespace) }
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 6c281030618..7f6c47d675b 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -401,6 +401,40 @@ RSpec.describe ProjectPolicy do
end
end
+ describe 'bot_log_in' do
+ let(:bot_user) { create(:user, :project_bot) }
+ let(:project) { private_project }
+
+ context 'when bot is in project and is not blocked' do
+ before do
+ project.add_maintainer(bot_user)
+ end
+
+ it 'is a valid project bot' do
+ expect(bot_user.can?(:bot_log_in, project)).to be_truthy
+ end
+ end
+
+ context 'when project bot is invalid' do
+ context 'when bot is not in project' do
+ it 'is not a valid project bot' do
+ expect(bot_user.can?(:bot_log_in, project)).to be_falsy
+ end
+ end
+
+ context 'when bot user is blocked' do
+ before do
+ project.add_maintainer(bot_user)
+ bot_user.block!
+ end
+
+ it 'is not a valid project bot' do
+ expect(bot_user.can?(:bot_log_in, project)).to be_falsy
+ end
+ end
+ end
+ end
+
context 'support bot' do
let(:current_user) { User.support_bot }
@@ -943,5 +977,145 @@ RSpec.describe ProjectPolicy do
end
end
+ describe 'read_analytics' do
+ context 'anonymous user' do
+ let(:current_user) { anonymous }
+
+ it { is_expected.to be_allowed(:read_analytics) }
+ end
+
+ context 'project member' do
+ let(:project) { private_project }
+
+ %w(guest reporter developer maintainer).each do |role|
+ context role do
+ let(:current_user) { send(role) }
+
+ it { is_expected.to be_allowed(:read_analytics) }
+
+ context "without access to Analytics" do
+ before do
+ project.project_feature.update!(analytics_access_level: ProjectFeature::DISABLED)
+ end
+
+ it { is_expected.to be_disallowed(:read_analytics) }
+ end
+ end
+ end
+ end
+ end
+
it_behaves_like 'Self-managed Core resource access tokens'
+
+ describe 'operations feature' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:guest_operations_permissions) { [:read_environment, :read_deployment] }
+
+ let(:developer_operations_permissions) do
+ guest_operations_permissions + [
+ :read_feature_flag, :read_sentry_issue, :read_alert_management_alert, :read_terraform_state,
+ :metrics_dashboard, :read_pod_logs, :read_prometheus, :create_feature_flag,
+ :create_environment, :create_deployment, :update_feature_flag, :update_environment,
+ :update_sentry_issue, :update_alert_management_alert, :update_deployment,
+ :destroy_feature_flag, :destroy_environment, :admin_feature_flag
+ ]
+ end
+
+ let(:maintainer_operations_permissions) do
+ developer_operations_permissions + [
+ :read_cluster, :create_cluster, :update_cluster, :admin_environment,
+ :admin_cluster, :admin_terraform_state, :admin_deployment
+ ]
+ end
+
+ where(:project_visibility, :access_level, :role, :allowed) do
+ :public | ProjectFeature::ENABLED | :maintainer | true
+ :public | ProjectFeature::ENABLED | :developer | true
+ :public | ProjectFeature::ENABLED | :guest | true
+ :public | ProjectFeature::ENABLED | :anonymous | true
+ :public | ProjectFeature::PRIVATE | :maintainer | true
+ :public | ProjectFeature::PRIVATE | :developer | true
+ :public | ProjectFeature::PRIVATE | :guest | true
+ :public | ProjectFeature::PRIVATE | :anonymous | false
+ :public | ProjectFeature::DISABLED | :maintainer | false
+ :public | ProjectFeature::DISABLED | :developer | false
+ :public | ProjectFeature::DISABLED | :guest | false
+ :public | ProjectFeature::DISABLED | :anonymous | false
+ :internal | ProjectFeature::ENABLED | :maintainer | true
+ :internal | ProjectFeature::ENABLED | :developer | true
+ :internal | ProjectFeature::ENABLED | :guest | true
+ :internal | ProjectFeature::ENABLED | :anonymous | false
+ :internal | ProjectFeature::PRIVATE | :maintainer | true
+ :internal | ProjectFeature::PRIVATE | :developer | true
+ :internal | ProjectFeature::PRIVATE | :guest | true
+ :internal | ProjectFeature::PRIVATE | :anonymous | false
+ :internal | ProjectFeature::DISABLED | :maintainer | false
+ :internal | ProjectFeature::DISABLED | :developer | false
+ :internal | ProjectFeature::DISABLED | :guest | false
+ :internal | ProjectFeature::DISABLED | :anonymous | false
+ :private | ProjectFeature::ENABLED | :maintainer | true
+ :private | ProjectFeature::ENABLED | :developer | true
+ :private | ProjectFeature::ENABLED | :guest | false
+ :private | ProjectFeature::ENABLED | :anonymous | false
+ :private | ProjectFeature::PRIVATE | :maintainer | true
+ :private | ProjectFeature::PRIVATE | :developer | true
+ :private | ProjectFeature::PRIVATE | :guest | false
+ :private | ProjectFeature::PRIVATE | :anonymous | false
+ :private | ProjectFeature::DISABLED | :maintainer | false
+ :private | ProjectFeature::DISABLED | :developer | false
+ :private | ProjectFeature::DISABLED | :guest | false
+ :private | ProjectFeature::DISABLED | :anonymous | false
+ end
+
+ with_them do
+ let(:current_user) { user_subject(role) }
+ let(:project) { project_subject(project_visibility) }
+
+ it 'allows/disallows the abilities based on the operation feature access level' do
+ project.project_feature.update!(operations_access_level: access_level)
+
+ if allowed
+ expect_allowed(*permissions_abilities(role))
+ else
+ expect_disallowed(*permissions_abilities(role))
+ end
+ end
+
+ def project_subject(project_type)
+ case project_type
+ when :public
+ public_project
+ when :internal
+ internal_project
+ else
+ private_project
+ end
+ end
+
+ def user_subject(role)
+ case role
+ when :maintainer
+ maintainer
+ when :developer
+ developer
+ when :guest
+ guest
+ when :anonymous
+ anonymous
+ end
+ end
+
+ def permissions_abilities(role)
+ case role
+ when :maintainer
+ maintainer_operations_permissions
+ when :developer
+ developer_operations_permissions
+ else
+ guest_operations_permissions
+ end
+ end
+ end
+ end
end
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 17ac7d0e44d..78212f06526 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -160,4 +160,16 @@ RSpec.describe UserPolicy do
it { is_expected.not_to be_allowed(:read_group_count) }
end
end
+
+ describe ':read_user_profile' do
+ context 'when the user is unconfirmed' do
+ let(:user) { create(:user, :unconfirmed) }
+
+ it { is_expected.not_to be_allowed(:read_user_profile) }
+ end
+
+ context 'when the user is confirmed' do
+ it { is_expected.to be_allowed(:read_user_profile) }
+ end
+ end
end
diff --git a/spec/presenters/gitlab/whats_new/item_presenter_spec.rb b/spec/presenters/gitlab/whats_new/item_presenter_spec.rb
new file mode 100644
index 00000000000..9b04741aa60
--- /dev/null
+++ b/spec/presenters/gitlab/whats_new/item_presenter_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::WhatsNew::ItemPresenter do
+ let(:present) { Gitlab::WhatsNew::ItemPresenter.present(item) }
+ let(:item) { { "packages" => %w(Core Starter Premium Ultimate) } }
+ let(:gitlab_com) { true }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(gitlab_com)
+ end
+
+ describe '.present' do
+ context 'when on Gitlab.com' do
+ it 'transforms package names to gitlab.com friendly package names' do
+ expect(present).to eq({ "packages" => %w(Free Bronze Silver Gold) })
+ end
+ end
+
+ context 'when not on Gitlab.com' do
+ let(:gitlab_com) { false }
+
+ it 'does not transform package names' do
+ expect(present).to eq({ "packages" => %w(Core Starter Premium Ultimate) })
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/composer/packages_presenter_spec.rb b/spec/presenters/packages/composer/packages_presenter_spec.rb
index 0445a346180..19d99a62468 100644
--- a/spec/presenters/packages/composer/packages_presenter_spec.rb
+++ b/spec/presenters/packages/composer/packages_presenter_spec.rb
@@ -67,7 +67,7 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do
{
'packages' => [],
'provider-includes' => { 'p/%hash%.json' => { 'sha256' => /^\h+$/ } },
- 'providers-url' => "/api/v4/group/#{group.id}/-/packages/composer/%package%.json"
+ 'providers-url' => "/api/v4/group/#{group.id}/-/packages/composer/%package%$%hash%.json"
}
end
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index b7fee5253f8..a9050c233af 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -375,7 +375,7 @@ RSpec.describe ProjectPresenter do
it 'returns anchor data' do
project.add_developer(user)
- allow(project.repository).to receive(:readme).and_return(nil)
+ allow(project.repository).to receive(:readme_path).and_return(nil)
expect(presenter.readme_anchor_data).to have_attributes(
is_link: false,
@@ -387,7 +387,7 @@ RSpec.describe ProjectPresenter do
context 'when README exists' do
it 'returns anchor data' do
- allow(project.repository).to receive(:readme).and_return(double(name: 'readme'))
+ allow(project.repository).to receive(:readme_path).and_return('readme')
expect(presenter.readme_anchor_data).to have_attributes(
is_link: false,
@@ -561,7 +561,7 @@ RSpec.describe ProjectPresenter do
let(:project) { build_stubbed(:project) }
it 'orders the items correctly' do
- allow(project.repository).to receive(:readme).and_return(double(name: 'readme'))
+ allow(project.repository).to receive(:readme_path).and_return('readme')
allow(project.repository).to receive(:license_blob).and_return(nil)
allow(project.repository).to receive(:changelog).and_return(nil)
allow(project.repository).to receive(:contribution_guide).and_return(double(name: 'foo'))
diff --git a/spec/presenters/projects/import_export/project_export_presenter_spec.rb b/spec/presenters/projects/import_export/project_export_presenter_spec.rb
index 8463d01d95b..b2b2ce35f34 100644
--- a/spec/presenters/projects/import_export/project_export_presenter_spec.rb
+++ b/spec/presenters/projects/import_export/project_export_presenter_spec.rb
@@ -45,6 +45,14 @@ RSpec.describe Projects::ImportExport::ProjectExportPresenter do
end
end
+ describe '#protected_branches' do
+ it 'returns the project exported protected branches' do
+ expect(project).to receive(:exported_protected_branches)
+
+ subject.protected_branches
+ end
+ end
+
describe '#project_members' do
let(:user2) { create(:user, email: 'group@member.com') }
let(:member_emails) do
diff --git a/spec/presenters/search_service_presenter_spec.rb b/spec/presenters/search_service_presenter_spec.rb
new file mode 100644
index 00000000000..06ece838d8d
--- /dev/null
+++ b/spec/presenters/search_service_presenter_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe SearchServicePresenter do
+ let(:user) { create(:user) }
+ let(:search_service) { SearchService.new(user, search: search, scope: scope) }
+ let(:presenter) { described_class.new(search_service, current_user: user) }
+
+ describe '#show_results_status?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:search) { '' }
+ let(:scope) { nil }
+
+ before do
+ allow(presenter).to receive(:search_objects).and_return([])
+ allow(presenter).to receive(:without_count?).and_return(!with_count)
+ allow(presenter).to receive(:show_snippets?).and_return(show_snippets)
+ allow(presenter).to receive(:show_sort_dropdown?).and_return(show_sort_dropdown)
+ end
+
+ where(:with_count, :show_snippets, :show_sort_dropdown, :result) do
+ true | true | true | true
+ false | true | false | true
+ false | false | true | true
+ false | false | false | false
+ end
+
+ with_them do
+ it { expect(presenter.show_results_status?).to eq(result) }
+ end
+ end
+end
diff --git a/spec/presenters/snippet_presenter_spec.rb b/spec/presenters/snippet_presenter_spec.rb
index 66c6ba8fa0e..a1d987ed78f 100644
--- a/spec/presenters/snippet_presenter_spec.rb
+++ b/spec/presenters/snippet_presenter_spec.rb
@@ -72,7 +72,7 @@ RSpec.describe SnippetPresenter do
context 'with ProjectSnippet' do
let(:snippet) { project_snippet }
- it 'checks read_snippet ' do
+ it 'checks read_snippet' do
expect(presenter).to receive(:can?).with(user, :read_snippet, snippet)
subject
@@ -96,7 +96,7 @@ RSpec.describe SnippetPresenter do
context 'with ProjectSnippet' do
let(:snippet) { project_snippet }
- it 'checks update_snippet ' do
+ it 'checks update_snippet' do
expect(presenter).to receive(:can?).with(user, :update_snippet, snippet)
subject
@@ -120,7 +120,7 @@ RSpec.describe SnippetPresenter do
context 'with ProjectSnippet' do
let(:snippet) { project_snippet }
- it 'checks admin_snippet ' do
+ it 'checks admin_snippet' do
expect(presenter).to receive(:can?).with(user, :admin_snippet, snippet)
subject
diff --git a/spec/requests/api/admin/instance_clusters_spec.rb b/spec/requests/api/admin/instance_clusters_spec.rb
index 1052080aad4..ab3b6b718e1 100644
--- a/spec/requests/api/admin/instance_clusters_spec.rb
+++ b/spec/requests/api/admin/instance_clusters_spec.rb
@@ -90,6 +90,8 @@ RSpec.describe ::API::Admin::InstanceClusters do
expect(json_response['environment_scope']).to eq('*')
expect(json_response['cluster_type']).to eq('instance_type')
expect(json_response['domain']).to eq('example.com')
+ expect(json_response['enabled']).to be_truthy
+ expect(json_response['managed']).to be_truthy
end
it 'returns kubernetes platform information' do
@@ -163,6 +165,7 @@ RSpec.describe ::API::Admin::InstanceClusters do
name: 'test-instance-cluster',
domain: 'domain.example.com',
managed: false,
+ enabled: false,
namespace_per_environment: false,
platform_kubernetes_attributes: platform_kubernetes_attributes,
clusterable: clusterable
@@ -205,9 +208,9 @@ RSpec.describe ::API::Admin::InstanceClusters do
expect(cluster_result.name).to eq('test-instance-cluster')
expect(cluster_result.domain).to eq('domain.example.com')
expect(cluster_result.environment_scope).to eq('*')
- expect(cluster_result.enabled).to eq(true)
- expect(platform_kubernetes.authorization_type).to eq('rbac')
expect(cluster_result.managed).to be_falsy
+ expect(cluster_result.enabled).to be_falsy
+ expect(platform_kubernetes.authorization_type).to eq('rbac')
expect(cluster_result.namespace_per_environment).to eq(false)
expect(platform_kubernetes.api_url).to eq("https://example.com")
expect(platform_kubernetes.token).to eq('sample-token')
@@ -301,6 +304,8 @@ RSpec.describe ::API::Admin::InstanceClusters do
let(:update_params) do
{
domain: domain,
+ managed: false,
+ enabled: false,
platform_kubernetes_attributes: platform_kubernetes_attributes
}
end
@@ -326,6 +331,8 @@ RSpec.describe ::API::Admin::InstanceClusters do
it 'updates cluster attributes' do
expect(cluster.domain).to eq('new-domain.com')
+ expect(cluster.managed).to be_falsy
+ expect(cluster.enabled).to be_falsy
end
end
@@ -338,6 +345,8 @@ RSpec.describe ::API::Admin::InstanceClusters do
it 'does not update cluster attributes' do
expect(cluster.domain).to eq('old-domain.com')
+ expect(cluster.managed).to be_truthy
+ expect(cluster.enabled).to be_truthy
end
it 'returns validation errors' do
diff --git a/spec/requests/api/api_guard/admin_mode_middleware_spec.rb b/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
index 4b477f829a7..63bcec4b52a 100644
--- a/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
+++ b/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe API::APIGuard::AdminModeMiddleware, :do_not_mock_admin_mode, :request_store do
+RSpec.describe API::APIGuard::AdminModeMiddleware, :request_store do
let(:user) { create(:admin) }
it 'is loaded' do
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
index b5b6ce106e5..b023ec398a2 100644
--- a/spec/requests/api/broadcast_messages_spec.rb
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -112,7 +112,7 @@ RSpec.describe API::BroadcastMessages do
expect(response).to have_gitlab_http_status(:bad_request)
end
- it 'accepts an active dismissable value ' do
+ it 'accepts an active dismissable value' do
attrs = { message: 'new message', dismissable: true }
post api('/broadcast_messages', admin), params: attrs
@@ -197,7 +197,7 @@ RSpec.describe API::BroadcastMessages do
expect(response).to have_gitlab_http_status(:bad_request)
end
- it 'accepts a new dismissable value ' do
+ it 'accepts a new dismissable value' do
attrs = { message: 'new message', dismissable: true }
put api("/broadcast_messages/#{message.id}", admin), params: attrs
diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
index 71be0c30f5a..4d8da50f8f0 100644
--- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
@@ -242,7 +242,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
}
expect { authorize_artifacts_with_token_in_headers(artifact_type: :lsif) }
- .to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(tracking_params) }
+ .to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**tracking_params) }
.by(1)
end
end
diff --git a/spec/requests/api/ci/runner/jobs_put_spec.rb b/spec/requests/api/ci/runner/jobs_put_spec.rb
index cbefaa2c321..e9d793d5a22 100644
--- a/spec/requests/api/ci/runner/jobs_put_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_put_spec.rb
@@ -61,6 +61,23 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(response.header).not_to have_key('X-GitLab-Trace-Update-Interval')
end
+ context 'when runner sends an unrecognized field in a payload' do
+ ##
+ # This test case is here to ensure that the API used to communicate
+ # runner with GitLab can evolve.
+ #
+ # In case of adding new features on the Runner side we do not want
+ # GitLab-side to reject requests containing unrecognizable fields in
+ # a payload, because runners can be updated before a new version of
+ # GitLab is installed.
+ #
+ it 'ignores unrecognized fields' do
+ update_job(state: 'success', 'unknown': 'something')
+
+ expect(job.reload).to be_success
+ end
+ end
+
context 'when failure_reason is script_failure' do
before do
update_job(state: 'failed', failure_reason: 'script_failure')
diff --git a/spec/requests/api/ci/runner/jobs_trace_spec.rb b/spec/requests/api/ci/runner/jobs_trace_spec.rb
index 1980c1a9f51..5b7a33d23d8 100644
--- a/spec/requests/api/ci/runner/jobs_trace_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_trace_spec.rb
@@ -135,7 +135,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
patch_the_trace
end
- it 'returns Forbidden ' do
+ it 'returns Forbidden' do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb
index ef4682466d5..06d4a2c6017 100644
--- a/spec/requests/api/composer_packages_spec.rb
+++ b/spec/requests/api/composer_packages_spec.rb
@@ -167,7 +167,8 @@ RSpec.describe API::ComposerPackages do
describe 'GET /api/v4/group/:id/-/packages/composer/*package_name.json' do
let(:package_name) { 'foobar' }
- let(:url) { "/group/#{group.id}/-/packages/composer/#{package_name}.json" }
+ let(:sha) { '$1234' }
+ let(:url) { "/group/#{group.id}/-/packages/composer/#{package_name}#{sha}.json" }
subject { get api(url), headers: headers }
@@ -206,6 +207,16 @@ RSpec.describe API::ComposerPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
+
+ context 'without a sha' do
+ let(:sha) { '' }
+
+ include_context 'Composer api group access', 'PRIVATE', :developer, true do
+ include_context 'Composer user type', :developer, true do
+ it_behaves_like 'process Composer api request', :developer, :not_found, true
+ end
+ end
+ end
end
it_behaves_like 'rejects Composer access with unknown group id'
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index 720ea429c2c..bdfc1589c9e 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -61,6 +61,37 @@ RSpec.describe API::Discussions do
expect(response).to have_gitlab_http_status(:bad_request)
end
end
+
+ context "when a commit parameter is given" do
+ it "creates the discussion on that commit within the merge request" do
+ # SHAs of "feature" and its parent in spec/support/gitlab-git-test.git
+ mr_commit = '0b4bc9a49b562e85de7cc9e834518ea6828729b9'
+ parent_commit = 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f'
+ file = "files/ruby/feature.rb"
+ line_range = {
+ "start_line_code" => Gitlab::Git.diff_line_code(file, 1, 1),
+ "end_line_code" => Gitlab::Git.diff_line_code(file, 1, 1),
+ "start_line_type" => "text",
+ "end_line_type" => "text"
+ }
+ position = build(
+ :text_diff_position,
+ :added,
+ file: file,
+ new_line: 1,
+ line_range: line_range,
+ base_sha: parent_commit,
+ head_sha: mr_commit,
+ start_sha: parent_commit
+ )
+
+ post api("/projects/#{project.id}/merge_requests/#{noteable['iid']}/discussions", user),
+ params: { body: 'MR discussion on commit', position: position.to_h, commit_id: mr_commit }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['notes'].first['commit_id']).to eq(mr_commit)
+ end
+ end
end
context 'when noteable is a Commit' do
diff --git a/spec/requests/api/feature_flags_spec.rb b/spec/requests/api/feature_flags_spec.rb
index 90d4a7b8b21..dd12648f4dd 100644
--- a/spec/requests/api/feature_flags_spec.rb
+++ b/spec/requests/api/feature_flags_spec.rb
@@ -65,26 +65,6 @@ RSpec.describe API::FeatureFlags do
expect(json_response.map { |f| f['version'] }).to eq(%w[legacy_flag legacy_flag])
end
- it 'does not return the legacy flag version when the feature flag is disabled' do
- stub_feature_flags(feature_flags_new_version: false)
-
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/feature_flags')
- expect(json_response.select { |f| f.key?('version') }).to eq([])
- end
-
- it 'does not return strategies if the new flag is disabled' do
- stub_feature_flags(feature_flags_new_version: false)
-
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/feature_flags')
- expect(json_response.select { |f| f.key?('strategies') }).to eq([])
- end
-
it 'does not have N+1 problem' do
control_count = ActiveRecord::QueryRecorder.new { subject }
@@ -134,16 +114,6 @@ RSpec.describe API::FeatureFlags do
}]
}])
end
-
- it 'does not return a version 2 flag when the feature flag is disabled' do
- stub_feature_flags(feature_flags_new_version: false)
-
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/feature_flags')
- expect(json_response).to eq([])
- end
end
context 'with version 1 and 2 feature flags' do
@@ -159,20 +129,6 @@ RSpec.describe API::FeatureFlags do
expect(response).to match_response_schema('public_api/v4/feature_flags')
expect(json_response.map { |f| f['name'] }).to eq(%w[legacy_flag new_version_flag])
end
-
- it 'returns only version 1 flags when the feature flag is disabled' do
- stub_feature_flags(feature_flags_new_version: false)
- create(:operations_feature_flag, project: project, name: 'legacy_flag')
- feature_flag = create(:operations_feature_flag, :new_version_flag, project: project, name: 'new_version_flag')
- strategy = create(:operations_strategy, feature_flag: feature_flag, name: 'default', parameters: {})
- create(:operations_scope, strategy: strategy, environment_scope: 'production')
-
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/feature_flags')
- expect(json_response.map { |f| f['name'] }).to eq(['legacy_flag'])
- end
end
end
@@ -224,18 +180,6 @@ RSpec.describe API::FeatureFlags do
}]
})
end
-
- it 'returns a 404 when the feature is disabled' do
- stub_feature_flags(feature_flags_new_version: false)
- feature_flag = create(:operations_feature_flag, :new_version_flag, project: project, name: 'feature1')
- strategy = create(:operations_strategy, feature_flag: feature_flag, name: 'default', parameters: {})
- create(:operations_scope, strategy: strategy, environment_scope: 'production')
-
- get api("/projects/#{project.id}/feature_flags/feature1", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response).to eq({ 'message' => '404 Not found' })
- end
end
end
@@ -290,16 +234,6 @@ RSpec.describe API::FeatureFlags do
expect(json_response['version']).to eq('legacy_flag')
end
- it 'does not return version when new version flags are disabled' do
- stub_feature_flags(feature_flags_new_version: false)
-
- subject
-
- expect(response).to have_gitlab_http_status(:created)
- expect(response).to match_response_schema('public_api/v4/feature_flag')
- expect(json_response.key?('version')).to eq(false)
- end
-
context 'with active set to false in the params for a legacy flag' do
let(:params) do
{
@@ -505,20 +439,6 @@ RSpec.describe API::FeatureFlags do
environment_scope: 'staging'
}])
end
-
- it 'returns a 422 when the feature flag is disabled' do
- stub_feature_flags(feature_flags_new_version: false)
- params = {
- name: 'new-feature',
- version: 'new_version_flag'
- }
-
- post api("/projects/#{project.id}/feature_flags", user), params: params
-
- expect(response).to have_gitlab_http_status(:unprocessable_entity)
- expect(json_response).to eq({ 'message' => 'Version 2 flags are not enabled for this project' })
- expect(project.operations_feature_flags.count).to eq(0)
- end
end
context 'when given invalid parameters' do
@@ -744,16 +664,6 @@ RSpec.describe API::FeatureFlags do
name: 'feature1', description: 'old description')
end
- it 'returns a 404 if the feature is disabled' do
- stub_feature_flags(feature_flags_new_version: false)
- params = { description: 'new description' }
-
- put api("/projects/#{project.id}/feature_flags/feature1", user), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(feature_flag.reload.description).to eq('old description')
- end
-
it 'returns a 422' do
params = { description: 'new description' }
@@ -771,16 +681,6 @@ RSpec.describe API::FeatureFlags do
name: 'feature1', description: 'old description')
end
- it 'returns a 404 if the feature is disabled' do
- stub_feature_flags(feature_flags_new_version: false)
- params = { description: 'new description' }
-
- put api("/projects/#{project.id}/feature_flags/feature1", user), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(feature_flag.reload.description).to eq('old description')
- end
-
it 'returns a 404 if the feature flag does not exist' do
params = { description: 'new description' }
@@ -1100,15 +1000,6 @@ RSpec.describe API::FeatureFlags do
expect(json_response['version']).to eq('legacy_flag')
end
- it 'does not return version when new version flags are disabled' do
- stub_feature_flags(feature_flags_new_version: false)
-
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.key?('version')).to eq(false)
- end
-
context 'with a version 2 feature flag' do
let!(:feature_flag) { create(:operations_feature_flag, :new_version_flag, project: project) }
@@ -1117,14 +1008,6 @@ RSpec.describe API::FeatureFlags do
expect(response).to have_gitlab_http_status(:ok)
end
-
- it 'returns a 404 if the feature is disabled' do
- stub_feature_flags(feature_flags_new_version: false)
-
- expect { subject }.not_to change { Operations::FeatureFlag.count }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
end
end
end
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 3f443b4f92b..0e163ec2154 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -6,6 +6,18 @@ RSpec.describe API::Features, stub_feature_flags: false do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
+ # Find any `development` feature flag name
+ let(:known_feature_flag) do
+ Feature::Definition.definitions
+ .values.find(&:development?)
+ end
+
+ let(:known_feature_flag_definition_hash) do
+ a_hash_including(
+ 'type' => 'development'
+ )
+ end
+
before do
Feature.reset
Flipper.unregister_groups
@@ -22,12 +34,14 @@ RSpec.describe API::Features, stub_feature_flags: false do
{
'name' => 'feature_1',
'state' => 'on',
- 'gates' => [{ 'key' => 'boolean', 'value' => true }]
+ 'gates' => [{ 'key' => 'boolean', 'value' => true }],
+ 'definition' => nil
},
{
'name' => 'feature_2',
'state' => 'off',
- 'gates' => [{ 'key' => 'boolean', 'value' => false }]
+ 'gates' => [{ 'key' => 'boolean', 'value' => false }],
+ 'definition' => nil
},
{
'name' => 'feature_3',
@@ -35,7 +49,14 @@ RSpec.describe API::Features, stub_feature_flags: false do
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'groups', 'value' => ['perf_team'] }
- ]
+ ],
+ 'definition' => nil
+ },
+ {
+ 'name' => known_feature_flag.name,
+ 'state' => 'on',
+ 'gates' => [{ 'key' => 'boolean', 'value' => true }],
+ 'definition' => known_feature_flag_definition_hash
}
]
end
@@ -44,6 +65,7 @@ RSpec.describe API::Features, stub_feature_flags: false do
Feature.enable('feature_1')
Feature.disable('feature_2')
Feature.enable('feature_3', Feature.group(:perf_team))
+ Feature.enable(known_feature_flag.name)
end
it 'returns a 401 for anonymous users' do
@@ -67,7 +89,7 @@ RSpec.describe API::Features, stub_feature_flags: false do
end
describe 'POST /feature' do
- let(:feature_name) { 'my_feature' }
+ let(:feature_name) { known_feature_flag.name }
context 'when the feature does not exist' do
it 'returns a 401 for anonymous users' do
@@ -87,43 +109,55 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: 'true' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'on',
- 'gates' => [{ 'key' => 'boolean', 'value' => true }])
+ 'gates' => [{ 'key' => 'boolean', 'value' => true }],
+ 'definition' => known_feature_flag_definition_hash
+ )
+ end
+
+ it 'logs the event' do
+ expect(Feature.logger).to receive(:info).once
+
+ post api("/features/#{feature_name}", admin), params: { value: 'true' }
end
it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do
post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'conditional',
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'groups', 'value' => ['perf_team'] }
- ])
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
it 'creates an enabled feature for the given user when passed user=username' do
post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'conditional',
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'actors', 'value' => ["User:#{user.id}"] }
- ])
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
it 'creates an enabled feature for the given user and feature group when passed user=username and feature_group=perf_team' do
post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username, feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['name']).to eq('my_feature')
+ expect(json_response['name']).to eq(feature_name)
expect(json_response['state']).to eq('conditional')
expect(json_response['gates']).to contain_exactly(
{ 'key' => 'boolean', 'value' => false },
@@ -141,13 +175,15 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: 'true', project: project.full_path }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'conditional',
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'actors', 'value' => ["Project:#{project.id}"] }
- ])
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
end
@@ -156,12 +192,13 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: 'true', project: 'mep/to/the/mep/mep' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- "name" => "my_feature",
+ expect(json_response).to match(
+ "name" => feature_name,
"state" => "off",
"gates" => [
{ "key" => "boolean", "value" => false }
- ]
+ ],
+ 'definition' => known_feature_flag_definition_hash
)
end
end
@@ -175,13 +212,15 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: 'true', group: group.full_path }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'conditional',
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'actors', 'value' => ["Group:#{group.id}"] }
- ])
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
end
@@ -190,12 +229,13 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: 'true', group: 'not/a/group' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- "name" => "my_feature",
+ expect(json_response).to match(
+ "name" => feature_name,
"state" => "off",
"gates" => [
{ "key" => "boolean", "value" => false }
- ]
+ ],
+ 'definition' => known_feature_flag_definition_hash
)
end
end
@@ -205,26 +245,30 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: '50' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'conditional',
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'percentage_of_time', 'value' => 50 }
- ])
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
it 'creates a feature with the given percentage of actors if passed an integer' do
post api("/features/#{feature_name}", admin), params: { value: '50', key: 'percentage_of_actors' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'conditional',
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'percentage_of_actors', 'value' => 50 }
- ])
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
end
@@ -238,36 +282,42 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: 'true' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'on',
- 'gates' => [{ 'key' => 'boolean', 'value' => true }])
+ 'gates' => [{ 'key' => 'boolean', 'value' => true }],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do
post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'conditional',
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'groups', 'value' => ['perf_team'] }
- ])
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
it 'enables the feature for the given user when passed user=username' do
post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'conditional',
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'actors', 'value' => ["User:#{user.id}"] }
- ])
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
end
@@ -279,10 +329,12 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: 'false' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'off',
- 'gates' => [{ 'key' => 'boolean', 'value' => false }])
+ 'gates' => [{ 'key' => 'boolean', 'value' => false }],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
it 'disables the feature for the given Flipper group when passed feature_group=perf_team' do
@@ -292,10 +344,12 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: 'false', feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'off',
- 'gates' => [{ 'key' => 'boolean', 'value' => false }])
+ 'gates' => [{ 'key' => 'boolean', 'value' => false }],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
it 'disables the feature for the given user when passed user=username' do
@@ -305,10 +359,12 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: 'false', user: user.username }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'off',
- 'gates' => [{ 'key' => 'boolean', 'value' => false }])
+ 'gates' => [{ 'key' => 'boolean', 'value' => false }],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
end
@@ -321,13 +377,15 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: '30' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'conditional',
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'percentage_of_time', 'value' => 30 }
- ])
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
end
@@ -340,13 +398,15 @@ RSpec.describe API::Features, stub_feature_flags: false do
post api("/features/#{feature_name}", admin), params: { value: '74', key: 'percentage_of_actors' }
expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq(
- 'name' => 'my_feature',
+ expect(json_response).to match(
+ 'name' => feature_name,
'state' => 'conditional',
'gates' => [
{ 'key' => 'boolean', 'value' => false },
{ 'key' => 'percentage_of_actors', 'value' => 74 }
- ])
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
end
end
end
@@ -390,6 +450,12 @@ RSpec.describe API::Features, stub_feature_flags: false do
expect(response).to have_gitlab_http_status(:no_content)
end
+
+ it 'logs the event' do
+ expect(Feature.logger).to receive(:info).once
+
+ delete api("/features/#{feature_name}", admin)
+ end
end
end
end
diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb
index 9d422ebbce2..d45e24241b2 100644
--- a/spec/requests/api/go_proxy_spec.rb
+++ b/spec/requests/api/go_proxy_spec.rb
@@ -428,7 +428,7 @@ RSpec.describe API::GoProxy do
context 'with a non-existent project' do
def get_resource(user = nil, **params)
- get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, params)
+ get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, **params)
end
describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
@@ -465,7 +465,7 @@ RSpec.describe API::GoProxy do
end
def get_resource(user = nil, headers: {}, **params)
- get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, params), headers: headers
+ get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, **params), headers: headers
end
def fmt_pseudo_version(prefix, commit)
diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
index 5d5b963fed5..cd94ce91071 100644
--- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
@@ -66,28 +66,22 @@ RSpec.describe 'get board lists' do
describe 'sorting and pagination' do
let_it_be(:current_user) { user }
- let(:data_path) { [board_parent_type, :boards, :edges, 0, :node, :lists] }
+ let(:data_path) { [board_parent_type, :boards, :nodes, 0, :lists] }
- def pagination_query(params, page_info)
+ def pagination_query(params)
graphql_query_for(
board_parent_type,
{ 'fullPath' => board_parent.full_path },
<<~BOARDS
boards(first: 1) {
- edges {
- node {
- #{query_graphql_field('lists', params, "#{page_info} edges { node { id } }")}
- }
+ nodes {
+ #{query_graphql_field(:lists, params, "#{page_info} nodes { id }")}
}
}
BOARDS
)
end
- def pagination_results_data(data)
- data.map { |list| list.dig('node', 'id') }
- end
-
context 'when using default sorting' do
let!(:label_list) { create(:list, board: board, label: label, position: 10) }
let!(:label_list2) { create(:list, board: board, label: label2, position: 2) }
@@ -99,7 +93,7 @@ RSpec.describe 'get board lists' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { }
let(:first_param) { 2 }
- let(:expected_results) { lists.map { |list| list.to_global_id.to_s } }
+ let(:expected_results) { lists.map { |list| global_id_of(list) } }
end
end
end
diff --git a/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb b/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb
new file mode 100644
index 00000000000..e086ce02942
--- /dev/null
+++ b/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe 'Getting Ci Cd Setting' do
+ include GraphqlHelpers
+
+ let_it_be_with_reload(:project) { create(:project, :repository) }
+ let_it_be(:current_user) { project.owner }
+
+ let(:fields) do
+ <<~QUERY
+ #{all_graphql_fields_for('ProjectCiCdSetting')}
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('ciCdSettings', {}, fields)
+ )
+ end
+
+ let(:settings_data) { graphql_data['project']['ciCdSettings'] }
+
+ context 'without permissions' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_reporter(user)
+ post_graphql(query, current_user: user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ specify { expect(settings_data).to be nil }
+ end
+
+ context 'with project permissions' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ specify { expect(settings_data['mergePipelinesEnabled']).to eql project.ci_cd_settings.merge_pipelines_enabled? }
+ specify { expect(settings_data['mergeTrainsEnabled']).to eql project.ci_cd_settings.merge_trains_enabled? }
+ specify { expect(settings_data['project']['id']).to eql "gid://gitlab/Project/#{project.id}" }
+ end
+end
diff --git a/spec/requests/api/graphql/ci/config_spec.rb b/spec/requests/api/graphql/ci/config_spec.rb
new file mode 100644
index 00000000000..b682470e0a1
--- /dev/null
+++ b/spec/requests/api/graphql/ci/config_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.ciConfig' do
+ include GraphqlHelpers
+
+ subject(:post_graphql_query) { post_graphql(query, current_user: user) }
+
+ let(:user) { create(:user) }
+
+ let_it_be(:content) do
+ File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
+ end
+
+ let(:query) do
+ %(
+ query {
+ ciConfig(content: "#{content}") {
+ status
+ errors
+ stages {
+ name
+ groups {
+ name
+ size
+ jobs {
+ name
+ groupName
+ stage
+ needs {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ before do
+ post_graphql_query
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns the correct structure' do
+ expect(graphql_data['ciConfig']).to eq(
+ "status" => "VALID",
+ "errors" => [],
+ "stages" =>
+ [
+ {
+ "name" => "build",
+ "groups" =>
+ [
+ {
+ "name" => "rspec",
+ "size" => 2,
+ "jobs" =>
+ [
+ { "name" => "rspec 0 1", "groupName" => "rspec", "stage" => "build", "needs" => [] },
+ { "name" => "rspec 0 2", "groupName" => "rspec", "stage" => "build", "needs" => [] }
+ ]
+ },
+ {
+ "name" => "spinach", "size" => 1, "jobs" =>
+ [
+ { "name" => "spinach", "groupName" => "spinach", "stage" => "build", "needs" => [] }
+ ]
+ }
+ ]
+ },
+ {
+ "name" => "test",
+ "groups" =>
+ [
+ {
+ "name" => "docker",
+ "size" => 1,
+ "jobs" => [
+ { "name" => "docker", "groupName" => "docker", "stage" => "test", "needs" => [{ "name" => "spinach" }, { "name" => "rspec 0 1" }] }
+ ]
+ }
+ ]
+ }
+ ]
+ )
+ end
+end
diff --git a/spec/requests/api/graphql/ci/job_artifacts_spec.rb b/spec/requests/api/graphql/ci/job_artifacts_spec.rb
new file mode 100644
index 00000000000..df6e398fbe5
--- /dev/null
+++ b/spec/requests/api/graphql/ci/job_artifacts_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.project(fullPath).pipelines.jobs.artifacts' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:user) { create(:user) }
+
+ let_it_be(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ pipelines {
+ nodes {
+ jobs {
+ nodes {
+ artifacts {
+ nodes {
+ downloadPath
+ fileType
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns the fields for the artifacts' do
+ job = create(:ci_build, pipeline: pipeline)
+ create(:ci_job_artifact, :junit, job: job)
+
+ post_graphql(query, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ pipelines_data = graphql_data.dig('project', 'pipelines', 'nodes')
+ jobs_data = pipelines_data.first.dig('jobs', 'nodes')
+ artifact_data = jobs_data.first.dig('artifacts', 'nodes').first
+
+ expect(artifact_data['downloadPath']).to eq(
+ "/#{project.full_path}/-/jobs/#{job.id}/artifacts/download?file_type=junit"
+ )
+ expect(artifact_data['fileType']).to eq('JUNIT')
+ end
+end
diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb
index 618705e5f94..19954c4e52f 100644
--- a/spec/requests/api/graphql/ci/jobs_spec.rb
+++ b/spec/requests/api/graphql/ci/jobs_spec.rb
@@ -1,41 +1,44 @@
# frozen_string_literal: true
require 'spec_helper'
-RSpec.describe 'Query.project.pipeline.stages.groups.jobs' do
+RSpec.describe 'Query.project.pipeline' do
include GraphqlHelpers
- let(:project) { create(:project, :repository, :public) }
- let(:user) { create(:user) }
- let(:pipeline) do
- pipeline = create(:ci_pipeline, project: project, user: user)
- stage = create(:ci_stage_entity, pipeline: pipeline, name: 'first')
- create(:commit_status, stage_id: stage.id, pipeline: pipeline, name: 'my test job')
-
- pipeline
- end
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:user) { create(:user) }
def first(field)
[field.pluralize, 'nodes', 0]
end
- let(:jobs_graphql_data) { graphql_data.dig(*%w[project pipeline], *first('stage'), *first('group'), 'jobs', 'nodes') }
-
- let(:query) do
- %(
- query {
- project(fullPath: "#{project.full_path}") {
- pipeline(iid: "#{pipeline.iid}") {
- stages {
- nodes {
- name
- groups {
- nodes {
- name
- jobs {
- nodes {
- name
- pipeline {
- id
+ describe '.stages.groups.jobs' do
+ let(:pipeline) do
+ pipeline = create(:ci_pipeline, project: project, user: user)
+ stage = create(:ci_stage_entity, pipeline: pipeline, name: 'first')
+ create(:commit_status, stage_id: stage.id, pipeline: pipeline, name: 'my test job')
+
+ pipeline
+ end
+
+ let(:jobs_graphql_data) { graphql_data.dig(*%w[project pipeline], *first('stage'), *first('group'), 'jobs', 'nodes') }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ pipeline(iid: "#{pipeline.iid}") {
+ stages {
+ nodes {
+ name
+ groups {
+ nodes {
+ name
+ jobs {
+ nodes {
+ name
+ pipeline {
+ id
+ }
}
}
}
@@ -45,17 +48,15 @@ RSpec.describe 'Query.project.pipeline.stages.groups.jobs' do
}
}
}
- }
- )
- end
+ )
+ end
- it 'returns the jobs of a pipeline stage' do
- post_graphql(query, current_user: user)
+ it 'returns the jobs of a pipeline stage' do
+ post_graphql(query, current_user: user)
- expect(jobs_graphql_data).to contain_exactly(a_hash_including('name' => 'my test job'))
- end
+ expect(jobs_graphql_data).to contain_exactly(a_hash_including('name' => 'my test job'))
+ end
- context 'when fetching jobs from the pipeline' do
it 'avoids N+1 queries', :aggregate_failures do
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(query, current_user: user)
@@ -112,4 +113,50 @@ RSpec.describe 'Query.project.pipeline.stages.groups.jobs' do
])
end
end
+
+ describe '.jobs.artifacts' do
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ pipeline(iid: "#{pipeline.iid}") {
+ jobs {
+ nodes {
+ artifacts {
+ nodes {
+ downloadPath
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ context 'when the job is a build' do
+ it "returns the build's artifacts" do
+ create(:ci_build, :artifacts, pipeline: pipeline)
+
+ post_graphql(query, current_user: user)
+
+ job_data = graphql_data.dig('project', 'pipeline', 'jobs', 'nodes').first
+ expect(job_data.dig('artifacts', 'nodes').count).to be(2)
+ end
+ end
+
+ context 'when the job is not a build' do
+ it 'returns nil' do
+ create(:ci_bridge, pipeline: pipeline)
+
+ post_graphql(query, current_user: user)
+
+ job_data = graphql_data.dig('project', 'pipeline', 'jobs', 'nodes').first
+ expect(job_data['artifacts']).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/group/container_repositories_spec.rb b/spec/requests/api/graphql/group/container_repositories_spec.rb
index bcf689a5e8f..4aa775eba0f 100644
--- a/spec/requests/api/graphql/group/container_repositories_spec.rb
+++ b/spec/requests/api/graphql/group/container_repositories_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'getting container repositories in a group' do
let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten }
let_it_be(:container_expiration_policy) { project.container_expiration_policy }
- let(:fields) do
+ let(:container_repositories_fields) do
<<~GQL
edges {
node {
@@ -24,17 +24,25 @@ RSpec.describe 'getting container repositories in a group' do
GQL
end
+ let(:fields) do
+ <<~GQL
+ #{query_graphql_field('container_repositories', {}, container_repositories_fields)}
+ containerRepositoriesCount
+ GQL
+ end
+
let(:query) do
graphql_query_for(
'group',
{ 'fullPath' => group.full_path },
- query_graphql_field('container_repositories', {}, fields)
+ fields
)
end
let(:user) { owner }
let(:variables) { {} }
let(:container_repositories_response) { graphql_data.dig('group', 'containerRepositories', 'edges') }
+ let(:container_repositories_count_response) { graphql_data.dig('group', 'containerRepositoriesCount') }
before do
group.add_owner(owner)
@@ -101,7 +109,7 @@ RSpec.describe 'getting container repositories in a group' do
<<~GQL
query($path: ID!, $n: Int) {
group(fullPath: $path) {
- containerRepositories(first: $n) { #{fields} }
+ containerRepositories(first: $n) { #{container_repositories_fields} }
}
}
GQL
@@ -122,7 +130,7 @@ RSpec.describe 'getting container repositories in a group' do
<<~GQL
query($path: ID!, $name: String) {
group(fullPath: $path) {
- containerRepositories(name: $name) { #{fields} }
+ containerRepositories(name: $name) { #{container_repositories_fields} }
}
}
GQL
@@ -143,4 +151,10 @@ RSpec.describe 'getting container repositories in a group' do
expect(container_repositories_response.first.dig('node', 'id')).to eq(container_repository.to_global_id.to_s)
end
end
+
+ it 'returns the total count of container repositories' do
+ subject
+
+ expect(container_repositories_count_response).to eq(container_repositories.size)
+ end
end
diff --git a/spec/requests/api/graphql/group/group_members_spec.rb b/spec/requests/api/graphql/group/group_members_spec.rb
index 84b2fd63d46..3554e22cdf2 100644
--- a/spec/requests/api/graphql/group/group_members_spec.rb
+++ b/spec/requests/api/graphql/group/group_members_spec.rb
@@ -5,44 +5,95 @@ require 'spec_helper'
RSpec.describe 'getting group members information' do
include GraphqlHelpers
- let_it_be(:group) { create(:group, :public) }
+ let_it_be(:parent_group) { create(:group, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:user_1) { create(:user, username: 'user') }
let_it_be(:user_2) { create(:user, username: 'test') }
let(:member_data) { graphql_data['group']['groupMembers']['edges'] }
- before do
- [user_1, user_2].each { |user| group.add_guest(user) }
+ before_all do
+ [user_1, user_2].each { |user| parent_group.add_guest(user) }
end
context 'when the request is correct' do
it_behaves_like 'a working graphql query' do
- before do
- fetch_members(user)
+ before_all do
+ fetch_members
end
end
it 'returns group members successfully' do
- fetch_members(user)
+ fetch_members
expect(graphql_errors).to be_nil
- expect_array_response(user_1.to_global_id.to_s, user_2.to_global_id.to_s)
+ expect_array_response(user_1, user_2)
end
it 'returns members that match the search query' do
- fetch_members(user, { search: 'test' })
+ fetch_members(args: { search: 'test' })
expect(graphql_errors).to be_nil
- expect_array_response(user_2.to_global_id.to_s)
+ expect_array_response(user_2)
end
end
- def fetch_members(user = nil, args = {})
- post_graphql(members_query(args), current_user: user)
+ context 'member relations' do
+ let_it_be(:child_group) { create(:group, :public, parent: parent_group) }
+ let_it_be(:grandchild_group) { create(:group, :public, parent: child_group) }
+ let_it_be(:child_user) { create(:user) }
+ let_it_be(:grandchild_user) { create(:user) }
+
+ before_all do
+ child_group.add_guest(child_user)
+ grandchild_group.add_guest(grandchild_user)
+ end
+
+ it 'returns direct members' do
+ fetch_members(group: child_group, args: { relations: [:DIRECT] })
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(child_user)
+ end
+
+ it 'returns direct and inherited members' do
+ fetch_members(group: child_group, args: { relations: [:DIRECT, :INHERITED] })
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(child_user, user_1, user_2)
+ end
+
+ it 'returns direct, inherited, and descendant members' do
+ fetch_members(group: child_group, args: { relations: [:DIRECT, :INHERITED, :DESCENDANTS] })
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(child_user, user_1, user_2, grandchild_user)
+ end
+
+ it 'returns an error for an invalid member relation' do
+ fetch_members(group: child_group, args: { relations: [:OBLIQUE] })
+
+ expect(graphql_errors.first)
+ .to include('path' => %w[query group groupMembers relations],
+ 'message' => a_string_including('invalid value ([OBLIQUE])'))
+ end
+ end
+
+ context 'when unauthenticated' do
+ it 'returns nothing' do
+ fetch_members(current_user: nil)
+
+ expect(graphql_errors).to be_nil
+ expect(response).to have_gitlab_http_status(:success)
+ expect(member_data).to be_empty
+ end
+ end
+
+ def fetch_members(group: parent_group, current_user: user, args: {})
+ post_graphql(members_query(group.full_path, args), current_user: current_user)
end
- def members_query(args = {})
+ def members_query(group_path, args = {})
members_node = <<~NODE
edges {
node {
@@ -54,7 +105,7 @@ RSpec.describe 'getting group members information' do
NODE
graphql_query_for("group",
- { full_path: group.full_path },
+ { full_path: group_path },
[query_graphql_field("groupMembers", args, members_node)]
)
end
@@ -62,6 +113,7 @@ RSpec.describe 'getting group members information' do
def expect_array_response(*items)
expect(response).to have_gitlab_http_status(:success)
expect(member_data).to be_an Array
- expect(member_data.map { |node| node["node"]["user"]["id"] }).to match_array(items)
+ expect(member_data.map { |node| node["node"]["user"]["id"] })
+ .to match_array(items.map { |u| global_id_of(u) })
end
end
diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb
index 83180c7d7a5..391bae4cfcf 100644
--- a/spec/requests/api/graphql/group_query_spec.rb
+++ b/spec/requests/api/graphql/group_query_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# Based on spec/requests/api/groups_spec.rb
# Should follow closely in order to ensure all situations are covered
-RSpec.describe 'getting group information', :do_not_mock_admin_mode do
+RSpec.describe 'getting group information' do
include GraphqlHelpers
include UploadHelpers
diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb
index d7fa680d29b..42c8e0cc9c0 100644
--- a/spec/requests/api/graphql/issue/issue_spec.rb
+++ b/spec/requests/api/graphql/issue/issue_spec.rb
@@ -125,7 +125,7 @@ RSpec.describe 'Query.issue(id)' do
let(:issue_params) { { 'id' => confidential_issue.to_global_id.to_s } }
context 'when the user cannot see confidential issues' do
- it 'returns nil ' do
+ it 'returns nil' do
post_graphql(query, current_user: current_user)
expect(issue_data).to be nil
diff --git a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
index 4ad35e7f0d1..b8cde32877b 100644
--- a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
+++ b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
@@ -6,8 +6,9 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues do
include GraphqlHelpers
let_it_be(:admin) { create(:admin) }
+ let(:queue) { 'authorized_projects' }
- let(:variables) { { user: admin.username, queue_name: 'authorized_projects' } }
+ let(:variables) { { user: admin.username, queue_name: queue } }
let(:mutation) { graphql_mutation(:admin_sidekiq_queues_delete_jobs, variables) }
def mutation_response
@@ -26,18 +27,19 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues do
context 'valid request' do
around do |example|
- Sidekiq::Queue.new('authorized_projects').clear
+ Sidekiq::Queue.new(queue).clear
Sidekiq::Testing.disable!(&example)
- Sidekiq::Queue.new('authorized_projects').clear
+ Sidekiq::Queue.new(queue).clear
end
def add_job(user, args)
Sidekiq::Client.push(
'class' => 'AuthorizedProjectsWorker',
- 'queue' => 'authorized_projects',
+ 'queue' => queue,
'args' => args,
'meta.user' => user.username
)
+ raise 'Not enqueued!' if Sidekiq::Queue.new(queue).size.zero?
end
it 'returns info about the deleted jobs' do
@@ -55,7 +57,7 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues do
end
context 'when no required params are provided' do
- let(:variables) { { queue_name: 'authorized_projects' } }
+ let(:variables) { { queue_name: queue } }
it_behaves_like 'a mutation that returns errors in the response',
errors: ['No metadata provided']
diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
index 3aaebb5095a..b39062f2e71 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -56,10 +56,10 @@ RSpec.describe 'Adding an AwardEmoji' do
it_behaves_like 'a mutation that does not create an AwardEmoji'
it_behaves_like 'a mutation that returns top-level errors',
- errors: ['Cannot award emoji to this resource']
+ errors: ['You cannot award emoji to this resource.']
end
- context 'when the given awardable an Awardable' do
+ context 'when the given awardable is an Awardable' do
it 'creates an emoji' do
expect do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
index 6910ad80a11..170e7ff3b44 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe 'Toggling an AwardEmoji' do
it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
it_behaves_like 'a mutation that returns top-level errors',
- errors: ['Cannot award emoji to this resource']
+ errors: ['You cannot award emoji to this resource.']
end
context 'when the given awardable is an Awardable' do
diff --git a/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb b/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb
index 645edfc2e43..c4121cfed42 100644
--- a/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe 'Destroying a container repository' do
GQL
end
- let(:params) { { id: container_repository.to_global_id.to_s } }
+ let(:params) { { id: id } }
let(:mutation) { graphql_mutation(:destroy_container_repository, params, query) }
let(:mutation_response) { graphql_mutation_response(:destroyContainerRepository) }
let(:container_repository_mutation_response) { mutation_response['containerRepository'] }
diff --git a/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb b/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb
new file mode 100644
index 00000000000..decb2e7bccc
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Destroying a container repository tags' do
+ include_context 'container repository delete tags service shared context'
+ using RSpec::Parameterized::TableSyntax
+
+ include GraphqlHelpers
+
+ let(:id) { repository.to_global_id.to_s }
+ let(:tags) { %w[A C D E] }
+
+ let(:query) do
+ <<~GQL
+ deletedTagNames
+ errors
+ GQL
+ end
+
+ let(:params) { { id: id, tag_names: tags } }
+ let(:mutation) { graphql_mutation(:destroy_container_repository_tags, params, query) }
+ let(:mutation_response) { graphql_mutation_response(:destroyContainerRepositoryTags) }
+ let(:tag_names_response) { mutation_response['deletedTagNames'] }
+ let(:errors_response) { mutation_response['errors'] }
+
+ shared_examples 'destroying the container repository tags' do
+ before do
+ stub_delete_reference_requests(tags)
+ expect_delete_tag_by_names(tags)
+ allow_next_instance_of(ContainerRegistry::Client) do |client|
+ allow(client).to receive(:supports_tag_delete?).and_return(true)
+ end
+ end
+
+ it 'destroys the container repository tags' do
+ expect(Projects::ContainerRepository::DeleteTagsService)
+ .to receive(:new).and_call_original
+ expect { subject }.to change { ::Packages::Event.count }.by(1)
+
+ expect(tag_names_response).to eq(tags)
+ expect(errors_response).to eq([])
+ end
+
+ it_behaves_like 'returning response status', :success
+ end
+
+ shared_examples 'denying the mutation request' do
+ it 'does not destroy the container repository tags' do
+ expect(Projects::ContainerRepository::DeleteTagsService)
+ .not_to receive(:new)
+
+ expect { subject }.not_to change { ::Packages::Event.count }
+
+ expect(mutation_response).to be_nil
+ end
+
+ it_behaves_like 'returning response status', :success
+ end
+
+ describe 'post graphql mutation' do
+ subject { post_graphql_mutation(mutation, current_user: user) }
+
+ context 'with valid id' do
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'destroying the container repository tags'
+ :developer | 'destroying the container repository tags'
+ :reporter | 'denying the mutation request'
+ :guest | 'denying the mutation request'
+ :anonymous | 'denying the mutation request'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", user) unless user_role == :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
+
+ context 'with invalid id' do
+ let(:id) { 'gid://gitlab/ContainerRepository/5555' }
+
+ it_behaves_like 'denying the mutation request'
+ end
+
+ context 'with too many tags' do
+ let(:tags) { Array.new(Mutations::ContainerRepositories::DestroyTags::LIMIT + 1, 'x') }
+
+ it 'returns too many tags error' do
+ expect { subject }.not_to change { ::Packages::Event.count }
+
+ explanation = graphql_errors.dig(0, 'extensions', 'problems', 0, 'explanation')
+ expect(explanation).to eq(Mutations::ContainerRepositories::DestroyTags::TOO_MANY_TAGS_ERROR_MESSAGE)
+ end
+ end
+
+ context 'with service error' do
+ before do
+ project.add_maintainer(user)
+ allow_next_instance_of(Projects::ContainerRepository::DeleteTagsService) do |service|
+ allow(service).to receive(:execute).and_return(message: 'could not delete tags', status: :error)
+ end
+ end
+
+ it 'returns an error' do
+ subject
+
+ expect(tag_names_response).to eq([])
+ expect(errors_response).to eq(['could not delete tags'])
+ end
+
+ it 'does not create a package event' do
+ expect(::Packages::CreateEventService).not_to receive(:new)
+ expect { subject }.not_to change { ::Packages::Event.count }
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb b/spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb
new file mode 100644
index 00000000000..f25a49291a6
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Update Environment Canary Ingress', :clean_gitlab_redis_cache do
+ include GraphqlHelpers
+ include KubernetesHelpers
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:cluster) { create(:cluster, :project, projects: [project]) }
+ let_it_be(:service) { create(:cluster_platform_kubernetes, :configured, cluster: cluster) }
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:deployment) { create(:deployment, :success, environment: environment, project: project) }
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let(:environment_id) { environment.to_global_id.to_s }
+ let(:weight) { 25 }
+ let(:actor) { developer }
+
+ let(:mutation) do
+ graphql_mutation(:environments_canary_ingress_update, id: environment_id, weight: weight)
+ end
+
+ before_all do
+ project.add_maintainer(maintainer)
+ project.add_developer(developer)
+ end
+
+ before do
+ stub_kubeclient_ingresses(environment.deployment_namespace, response: kube_ingresses_response(with_canary: true))
+ end
+
+ context 'when kubernetes accepted the patch request' do
+ before do
+ stub_kubeclient_ingresses(environment.deployment_namespace, method: :patch, resource_path: "/production-auto-deploy")
+ end
+
+ it 'updates successfully' do
+ post_graphql_mutation(mutation, current_user: actor)
+
+ expect(graphql_mutation_response(:environments_canary_ingress_update)['errors'])
+ .to be_empty
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/releases/delete_spec.rb b/spec/requests/api/graphql/mutations/releases/delete_spec.rb
new file mode 100644
index 00000000000..3710f118bf4
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/releases/delete_spec.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Deleting a release' do
+ include GraphqlHelpers
+ include Presentable
+
+ let_it_be(:public_user) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:tag_name) { 'v1.1.0' }
+ let_it_be(:release) { create(:release, project: project, tag: tag_name) }
+
+ let(:mutation_name) { :release_delete }
+
+ let(:project_path) { project.full_path }
+ let(:mutation_arguments) do
+ {
+ projectPath: project_path,
+ tagName: tag_name
+ }
+ end
+
+ let(:mutation) do
+ graphql_mutation(mutation_name, mutation_arguments, <<~FIELDS)
+ release {
+ tagName
+ }
+ errors
+ FIELDS
+ end
+
+ let(:delete_release) { post_graphql_mutation(mutation, current_user: current_user) }
+ let(:mutation_response) { graphql_mutation_response(mutation_name)&.with_indifferent_access }
+
+ before do
+ project.add_guest(guest)
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+ project.add_maintainer(maintainer)
+ end
+
+ shared_examples 'unauthorized or not found error' do
+ it 'returns a top-level error with message' do
+ delete_release
+
+ expect(mutation_response).to be_nil
+ expect(graphql_errors.count).to eq(1)
+ expect(graphql_errors.first['message']).to eq("The resource that you are attempting to access does not exist or you don't have permission to perform this action")
+ end
+ end
+
+ context 'when the current user has access to update releases' do
+ let(:current_user) { maintainer }
+
+ it 'deletes the release' do
+ expect { delete_release }.to change { Release.count }.by(-1)
+ end
+
+ it 'returns the deleted release' do
+ delete_release
+
+ expected_release = { tagName: tag_name }.with_indifferent_access
+
+ expect(mutation_response[:release]).to eq(expected_release)
+ end
+
+ it 'does not remove the Git tag associated with the deleted release' do
+ expect { delete_release }.not_to change { Project.find_by_id(project.id).repository.tag_count }
+ end
+
+ it 'returns no errors' do
+ delete_release
+
+ expect(mutation_response[:errors]).to eq([])
+ end
+
+ context 'validation' do
+ context 'when the release does not exist' do
+ let_it_be(:tag_name) { 'not-a-real-release' }
+
+ it 'returns the release as null' do
+ delete_release
+
+ expect(mutation_response[:release]).to be_nil
+ end
+
+ it 'returns an errors-at-data message' do
+ delete_release
+
+ expect(mutation_response[:errors]).to eq(['Release does not exist'])
+ end
+ end
+
+ context 'when the project does not exist' do
+ let(:project_path) { 'not/a/real/path' }
+
+ it_behaves_like 'unauthorized or not found error'
+ end
+ end
+ end
+
+ context "when the current user doesn't have access to update releases" do
+ context 'when the current user is a Developer' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'unauthorized or not found error'
+ end
+
+ context 'when the current user is a Reporter' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'unauthorized or not found error'
+ end
+
+ context 'when the current user is a Guest' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'unauthorized or not found error'
+ end
+
+ context 'when the current user is a public user' do
+ let(:current_user) { public_user }
+
+ it_behaves_like 'unauthorized or not found error'
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/releases/update_spec.rb b/spec/requests/api/graphql/mutations/releases/update_spec.rb
new file mode 100644
index 00000000000..19320c3393c
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/releases/update_spec.rb
@@ -0,0 +1,255 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Updating an existing release' do
+ include GraphqlHelpers
+ include Presentable
+
+ let_it_be(:public_user) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:milestone_12_3) { create(:milestone, project: project, title: '12.3') }
+ let_it_be(:milestone_12_4) { create(:milestone, project: project, title: '12.4') }
+
+ let_it_be(:tag_name) { 'v1.1.0' }
+ let_it_be(:name) { 'Version 7.12.5'}
+ let_it_be(:description) { 'Release 7.12.5 :rocket:' }
+ let_it_be(:released_at) { '2018-12-10' }
+ let_it_be(:created_at) { '2018-11-05' }
+ let_it_be(:milestones) { [milestone_12_3, milestone_12_4] }
+
+ let_it_be(:release) do
+ create(:release, project: project, tag: tag_name, name: name,
+ description: description, released_at: Time.parse(released_at).utc,
+ created_at: Time.parse(created_at).utc, milestones: milestones)
+ end
+
+ let(:mutation_name) { :release_update }
+
+ let(:mutation_arguments) do
+ {
+ projectPath: project.full_path,
+ tagName: tag_name
+ }
+ end
+
+ let(:mutation) do
+ graphql_mutation(mutation_name, mutation_arguments, <<~FIELDS)
+ release {
+ tagName
+ name
+ description
+ releasedAt
+ createdAt
+ milestones {
+ nodes {
+ title
+ }
+ }
+ }
+ errors
+ FIELDS
+ end
+
+ let(:update_release) { post_graphql_mutation(mutation, current_user: current_user) }
+ let(:mutation_response) { graphql_mutation_response(mutation_name)&.with_indifferent_access }
+
+ let(:expected_attributes) do
+ {
+ tagName: tag_name,
+ name: name,
+ description: description,
+ releasedAt: Time.parse(released_at).utc.iso8601,
+ createdAt: Time.parse(created_at).utc.iso8601,
+ milestones: {
+ nodes: milestones.map { |m| { title: m.title } }
+ }
+ }.with_indifferent_access
+ end
+
+ around do |example|
+ freeze_time { example.run }
+ end
+
+ before do
+ project.add_guest(guest)
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+
+ stub_default_url_options(host: 'www.example.com')
+ end
+
+ shared_examples 'no errors' do
+ it 'returns no errors' do
+ update_release
+
+ expect(graphql_errors).not_to be_present
+ end
+ end
+
+ shared_examples 'top-level error with message' do |error_message|
+ it 'returns a top-level error with message' do
+ update_release
+
+ expect(mutation_response).to be_nil
+ expect(graphql_errors.count).to eq(1)
+ expect(graphql_errors.first['message']).to eq(error_message)
+ end
+ end
+
+ shared_examples 'errors-as-data with message' do |error_message|
+ it 'returns an error-as-data with message' do
+ update_release
+
+ expect(mutation_response[:release]).to be_nil
+ expect(mutation_response[:errors].count).to eq(1)
+ expect(mutation_response[:errors].first).to match(error_message)
+ end
+ end
+
+ shared_examples 'updates release fields' do |updates|
+ it_behaves_like 'no errors'
+
+ it 'updates the correct field and returns the release' do
+ update_release
+
+ expect(mutation_response[:release]).to include(expected_attributes.merge(updates).except(:milestones))
+
+ # Right now the milestones are returned in a non-deterministic order.
+ # Because of this, we need to test milestones separately to allow
+ # for them to be returned in any order.
+ # Once https://gitlab.com/gitlab-org/gitlab/-/issues/259012 has been
+ # fixed, this special milestone handling can be removed.
+ expected_milestones = expected_attributes.merge(updates)[:milestones]
+ expect(mutation_response[:release][:milestones][:nodes]).to match_array(expected_milestones[:nodes])
+ end
+ end
+
+ context 'when the current user has access to update releases' do
+ let(:current_user) { developer }
+
+ context 'name' do
+ context 'when a new name is provided' do
+ let(:mutation_arguments) { super().merge(name: 'Updated name') }
+
+ it_behaves_like 'updates release fields', name: 'Updated name'
+ end
+
+ context 'when null is provided' do
+ let(:mutation_arguments) { super().merge(name: nil) }
+
+ it_behaves_like 'updates release fields', name: 'v1.1.0'
+ end
+ end
+
+ context 'description' do
+ context 'when a new description is provided' do
+ let(:mutation_arguments) { super().merge(description: 'Updated description') }
+
+ it_behaves_like 'updates release fields', description: 'Updated description'
+ end
+
+ context 'when null is provided' do
+ let(:mutation_arguments) { super().merge(description: nil) }
+
+ it_behaves_like 'updates release fields', description: nil
+ end
+ end
+
+ context 'releasedAt' do
+ context 'when no time zone is provided' do
+ let(:mutation_arguments) { super().merge(releasedAt: '2015-05-05') }
+
+ it_behaves_like 'updates release fields', releasedAt: Time.parse('2015-05-05').utc.iso8601
+ end
+
+ context 'when a local time zone is provided' do
+ let(:mutation_arguments) { super().merge(releasedAt: Time.parse('2015-05-05').in_time_zone('Hawaii').iso8601) }
+
+ it_behaves_like 'updates release fields', releasedAt: Time.parse('2015-05-05').utc.iso8601
+ end
+
+ context 'when null is provided' do
+ let(:mutation_arguments) { super().merge(releasedAt: nil) }
+
+ it_behaves_like 'top-level error with message', 'if the releasedAt argument is provided, it cannot be null'
+ end
+ end
+
+ context 'milestones' do
+ context 'when a new set of milestones is provided provided' do
+ let(:mutation_arguments) { super().merge(milestones: ['12.3']) }
+
+ it_behaves_like 'updates release fields', milestones: { nodes: [{ title: '12.3' }] }
+ end
+
+ context 'when an empty array is provided' do
+ let(:mutation_arguments) { super().merge(milestones: []) }
+
+ it_behaves_like 'updates release fields', milestones: { nodes: [] }
+ end
+
+ context 'when null is provided' do
+ let(:mutation_arguments) { super().merge(milestones: nil) }
+
+ it_behaves_like 'top-level error with message', 'if the milestones argument is provided, it cannot be null'
+ end
+
+ context 'when a non-existent milestone title is provided' do
+ let(:mutation_arguments) { super().merge(milestones: ['not real']) }
+
+ it_behaves_like 'errors-as-data with message', 'Milestone(s) not found: not real'
+ end
+
+ context 'when a milestone title from a different project is provided' do
+ let(:milestone_in_different_project) { create(:milestone, title: 'milestone in different project') }
+ let(:mutation_arguments) { super().merge(milestones: [milestone_in_different_project.title]) }
+
+ it_behaves_like 'errors-as-data with message', 'Milestone(s) not found: milestone in different project'
+ end
+ end
+
+ context 'validation' do
+ context 'when no updated fields are provided' do
+ it_behaves_like 'errors-as-data with message', 'params is empty'
+ end
+
+ context 'when the tag does not exist' do
+ let(:mutation_arguments) { super().merge(tagName: 'not-a-real-tag') }
+
+ it_behaves_like 'errors-as-data with message', 'Tag does not exist'
+ end
+
+ context 'when the project does not exist' do
+ let(:mutation_arguments) { super().merge(projectPath: 'not/a/real/path') }
+
+ it_behaves_like 'top-level error with message', "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ end
+ end
+ end
+
+ context "when the current user doesn't have access to update releases" do
+ expected_error_message = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+
+ context 'when the current user is a Reporter' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'top-level error with message', expected_error_message
+ end
+
+ context 'when the current user is a Guest' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'top-level error with message', expected_error_message
+ end
+
+ context 'when the current user is a public user' do
+ let(:current_user) { public_user }
+
+ it_behaves_like 'top-level error with message', expected_error_message
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
index d2fa3cfc24f..fd0dc98a8d3 100644
--- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
@@ -163,9 +163,15 @@ RSpec.describe 'Creating a Snippet' do
context 'when there are uploaded files' do
shared_examples 'expected files argument' do |file_value, expected_value|
let(:uploaded_files) { file_value }
+ let(:snippet) { build(:snippet) }
+ let(:creation_response) do
+ ::ServiceResponse.error(message: 'urk', payload: { snippet: snippet })
+ end
it do
- expect(::Snippets::CreateService).to receive(:new).with(nil, user, hash_including(files: expected_value))
+ expect(::Snippets::CreateService).to receive(:new)
+ .with(nil, user, hash_including(files: expected_value))
+ .and_return(double(execute: creation_response))
subject
end
diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
index 97e6ae8fda8..4d499310591 100644
--- a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
@@ -2,8 +2,9 @@
require 'spec_helper'
-RSpec.describe 'Mark snippet as spam', :do_not_mock_admin_mode do
+RSpec.describe 'Mark snippet as spam' do
include GraphqlHelpers
+ include AfterNextHelpers
let_it_be(:admin) { create(:admin) }
let_it_be(:other_user) { create(:user) }
@@ -56,11 +57,12 @@ RSpec.describe 'Mark snippet as spam', :do_not_mock_admin_mode do
end
it 'marks snippet as spam' do
- expect_next_instance_of(Spam::MarkAsSpamService) do |instance|
- expect(instance).to receive(:execute)
- end
+ expect_next(Spam::MarkAsSpamService, target: snippet)
+ .to receive(:execute).and_return(true)
post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors).to be_blank
end
end
end
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 03160719389..3e68503b7fb 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -80,38 +80,34 @@ RSpec.describe 'getting projects' do
end
describe 'sorting and pagination' do
+ let_it_be(:ns) { create(:group) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project_1) { create(:project, name: 'Project', path: 'project', namespace: ns) }
+ let_it_be(:project_2) { create(:project, name: 'Test Project', path: 'test-project', namespace: ns) }
+ let_it_be(:project_3) { create(:project, name: 'Test', path: 'test', namespace: ns) }
+ let_it_be(:project_4) { create(:project, name: 'Test Project Other', path: 'other-test-project', namespace: ns) }
+
let(:data_path) { [:namespace, :projects] }
- def pagination_query(params, page_info)
- graphql_query_for(
- 'namespace',
- { 'fullPath' => subject.full_path },
- <<~QUERY
- projects(includeSubgroups: #{include_subgroups}, search: "#{search}", #{params}) {
- #{page_info} edges {
- node {
- #{all_graphql_fields_for('Project')}
- }
- }
- }
- QUERY
- )
+ let(:ns_args) { { full_path: ns.full_path } }
+ let(:search) { 'test' }
+
+ before do
+ ns.add_owner(current_user)
end
- def pagination_results_data(data)
- data.map { |project| project.dig('node', 'name') }
+ def pagination_query(params)
+ arguments = params.merge(include_subgroups: include_subgroups, search: search)
+ graphql_query_for(:namespace, ns_args, query_graphql_field(:projects, arguments, <<~GQL))
+ #{page_info}
+ nodes { name }
+ GQL
end
context 'when sorting by similarity' do
- let!(:project_1) { create(:project, name: 'Project', path: 'project', namespace: subject) }
- let!(:project_2) { create(:project, name: 'Test Project', path: 'test-project', namespace: subject) }
- let!(:project_3) { create(:project, name: 'Test', path: 'test', namespace: subject) }
- let!(:project_4) { create(:project, name: 'Test Project Other', path: 'other-test-project', namespace: subject) }
- let(:search) { 'test' }
- let(:current_user) { user }
-
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'SIMILARITY' }
+ let(:node_path) { %w[name] }
+ let(:sort_param) { :SIMILARITY }
let(:first_param) { 2 }
let(:expected_results) { [project_3.name, project_2.name, project_4.name] }
end
diff --git a/spec/requests/api/graphql/project/container_repositories_spec.rb b/spec/requests/api/graphql/project/container_repositories_spec.rb
index 7e32f54bf1d..6b1c8689515 100644
--- a/spec/requests/api/graphql/project/container_repositories_spec.rb
+++ b/spec/requests/api/graphql/project/container_repositories_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe 'getting container repositories in a project' do
let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten }
let_it_be(:container_expiration_policy) { project.container_expiration_policy }
- let(:fields) do
+ let(:container_repositories_fields) do
<<~GQL
edges {
node {
@@ -22,17 +22,25 @@ RSpec.describe 'getting container repositories in a project' do
GQL
end
+ let(:fields) do
+ <<~GQL
+ #{query_graphql_field('container_repositories', {}, container_repositories_fields)}
+ containerRepositoriesCount
+ GQL
+ end
+
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
- query_graphql_field('container_repositories', {}, fields)
+ fields
)
end
let(:user) { project.owner }
let(:variables) { {} }
let(:container_repositories_response) { graphql_data.dig('project', 'containerRepositories', 'edges') }
+ let(:container_repositories_count_response) { graphql_data.dig('project', 'containerRepositoriesCount') }
before do
stub_container_registry_config(enabled: true)
@@ -100,7 +108,7 @@ RSpec.describe 'getting container repositories in a project' do
<<~GQL
query($path: ID!, $n: Int) {
project(fullPath: $path) {
- containerRepositories(first: $n) { #{fields} }
+ containerRepositories(first: $n) { #{container_repositories_fields} }
}
}
GQL
@@ -121,7 +129,7 @@ RSpec.describe 'getting container repositories in a project' do
<<~GQL
query($path: ID!, $name: String) {
project(fullPath: $path) {
- containerRepositories(name: $name) { #{fields} }
+ containerRepositories(name: $name) { #{container_repositories_fields} }
}
}
GQL
@@ -142,4 +150,10 @@ RSpec.describe 'getting container repositories in a project' do
expect(container_repositories_response.first.dig('node', 'id')).to eq(container_repository.to_global_id.to_s)
end
end
+
+ it 'returns the total count of container repositories' do
+ subject
+
+ expect(container_repositories_count_response).to eq(container_repositories.size)
+ end
end
diff --git a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
index 4bce3c7fe0f..f544d78ecbb 100644
--- a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
+++ b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
@@ -42,7 +42,6 @@ RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)
describe 'scalar fields' do
let(:path) { path_prefix }
- let(:version_fields) { query_graphql_field(:sha) }
before do
post_query
@@ -50,7 +49,7 @@ RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)
{ id: ->(x) { x.to_global_id.to_s }, sha: ->(x) { x.sha } }.each do |field, value|
describe ".#{field}" do
- let(:version_fields) { query_graphql_field(field) }
+ let(:version_fields) { field }
it "retrieves the #{field}" do
expect(data).to match(a_hash_including(field.to_s => value[version]))
diff --git a/spec/requests/api/graphql/project/issue_spec.rb b/spec/requests/api/graphql/project/issue_spec.rb
index 5f368833181..ddf63a8f2c9 100644
--- a/spec/requests/api/graphql/project/issue_spec.rb
+++ b/spec/requests/api/graphql/project/issue_spec.rb
@@ -29,8 +29,8 @@ RSpec.describe 'Query.project(fullPath).issue(iid)' do
let(:design_fields) do
[
- query_graphql_field(:filename),
- query_graphql_field(:project, nil, query_graphql_field(:id))
+ :filename,
+ query_graphql_field(:project, :id)
]
end
@@ -173,7 +173,7 @@ RSpec.describe 'Query.project(fullPath).issue(iid)' do
let(:result_fields) { { 'version' => id_hash(version) } }
let(:object_fields) do
- design_fields + [query_graphql_field(:version, nil, query_graphql_field(:id))]
+ design_fields + [query_graphql_field(:version, :id)]
end
let(:no_argument_error) { missing_required_argument(path, :id) }
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 4f27f08bf98..9c915075c42 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -142,16 +142,14 @@ RSpec.describe 'getting an issue list for a project' do
describe 'sorting and pagination' do
let_it_be(:data_path) { [:project, :issues] }
- def pagination_query(params, page_info)
- graphql_query_for(
- 'project',
- { 'fullPath' => sort_project.full_path },
- query_graphql_field('issues', params, "#{page_info} edges { node { iid dueDate} }")
+ def pagination_query(params)
+ graphql_query_for(:project, { full_path: sort_project.full_path },
+ query_graphql_field(:issues, params, "#{page_info} nodes { iid }")
)
end
def pagination_results_data(data)
- data.map { |issue| issue.dig('node', 'iid').to_i }
+ data.map { |issue| issue.dig('iid').to_i }
end
context 'when sorting by due date' do
@@ -164,7 +162,7 @@ RSpec.describe 'getting an issue list for a project' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'DUE_DATE_ASC' }
+ let(:sort_param) { :DUE_DATE_ASC }
let(:first_param) { 2 }
let(:expected_results) { [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid] }
end
@@ -172,7 +170,7 @@ RSpec.describe 'getting an issue list for a project' do
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'DUE_DATE_DESC' }
+ let(:sort_param) { :DUE_DATE_DESC }
let(:first_param) { 2 }
let(:expected_results) { [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid] }
end
@@ -189,7 +187,7 @@ RSpec.describe 'getting an issue list for a project' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'RELATIVE_POSITION_ASC' }
+ let(:sort_param) { :RELATIVE_POSITION_ASC }
let(:first_param) { 2 }
let(:expected_results) { [relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue4.iid, relative_issue2.iid] }
end
@@ -209,7 +207,7 @@ RSpec.describe 'getting an issue list for a project' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'PRIORITY_ASC' }
+ let(:sort_param) { :PRIORITY_ASC }
let(:first_param) { 2 }
let(:expected_results) { [priority_issue3.iid, priority_issue1.iid, priority_issue2.iid, priority_issue4.iid] }
end
@@ -217,7 +215,7 @@ RSpec.describe 'getting an issue list for a project' do
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'PRIORITY_DESC' }
+ let(:sort_param) { :PRIORITY_DESC }
let(:first_param) { 2 }
let(:expected_results) { [priority_issue1.iid, priority_issue3.iid, priority_issue2.iid, priority_issue4.iid] }
end
@@ -236,7 +234,7 @@ RSpec.describe 'getting an issue list for a project' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'LABEL_PRIORITY_ASC' }
+ let(:sort_param) { :LABEL_PRIORITY_ASC }
let(:first_param) { 2 }
let(:expected_results) { [label_issue3.iid, label_issue1.iid, label_issue2.iid, label_issue4.iid] }
end
@@ -244,7 +242,7 @@ RSpec.describe 'getting an issue list for a project' do
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'LABEL_PRIORITY_DESC' }
+ let(:sort_param) { :LABEL_PRIORITY_DESC }
let(:first_param) { 2 }
let(:expected_results) { [label_issue2.iid, label_issue3.iid, label_issue1.iid, label_issue4.iid] }
end
@@ -261,7 +259,7 @@ RSpec.describe 'getting an issue list for a project' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'MILESTONE_DUE_ASC' }
+ let(:sort_param) { :MILESTONE_DUE_ASC }
let(:first_param) { 2 }
let(:expected_results) { [milestone_issue2.iid, milestone_issue3.iid, milestone_issue1.iid] }
end
@@ -269,7 +267,7 @@ RSpec.describe 'getting an issue list for a project' do
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'MILESTONE_DUE_DESC' }
+ let(:sort_param) { :MILESTONE_DUE_DESC }
let(:first_param) { 2 }
let(:expected_results) { [milestone_issue3.iid, milestone_issue2.iid, milestone_issue1.iid] }
end
diff --git a/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb
new file mode 100644
index 00000000000..ac0b18a37d6
--- /dev/null
+++ b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.project.mergeRequests.pipelines' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:author) { create(:user) }
+ let_it_be(:merge_requests) do
+ %i[with_diffs with_image_diffs conflict].map do |trait|
+ create(:merge_request, trait, author: author, source_project: project)
+ end
+ end
+
+ describe '.count' do
+ let(:query) do
+ <<~GQL
+ query($path: ID!, $first: Int) {
+ project(fullPath: $path) {
+ mergeRequests(first: $first) {
+ nodes {
+ iid
+ pipelines {
+ count
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ def run_query(first = nil)
+ post_graphql(query, current_user: author, variables: { path: project.full_path, first: first })
+ end
+
+ before do
+ merge_requests.each do |mr|
+ shas = mr.all_commits.limit(2).pluck(:sha)
+
+ shas.each do |sha|
+ create(:ci_pipeline, :success, project: project, ref: mr.source_branch, sha: sha)
+ end
+ end
+ end
+
+ it 'produces correct results' do
+ run_query(2)
+
+ p_nodes = graphql_data_at(:project, :merge_requests, :nodes)
+
+ expect(p_nodes).to all(match('iid' => be_present, 'pipelines' => match('count' => 2)))
+ end
+
+ it 'is scalable', :request_store, :use_clean_rails_memory_store_caching do
+ # warm up
+ run_query
+
+ expect { run_query(2) }.to(issue_same_number_of_queries_as { run_query(1) }.ignoring_cached_queries)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index 2b8d537f9fc..c05a620bb62 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -259,29 +259,19 @@ RSpec.describe 'getting merge request listings nested in a project' do
describe 'sorting and pagination' do
let(:data_path) { [:project, :mergeRequests] }
- def pagination_query(params, page_info)
- graphql_query_for(
- :project,
- { full_path: project.full_path },
+ def pagination_query(params)
+ graphql_query_for(:project, { full_path: project.full_path },
<<~QUERY
mergeRequests(#{params}) {
- #{page_info} edges {
- node {
- id
- }
- }
+ #{page_info} nodes { id }
}
QUERY
)
end
- def pagination_results_data(data)
- data.map { |project| project.dig('node', 'id') }
- end
-
context 'when sorting by merged_at DESC' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'MERGED_AT_DESC' }
+ let(:sort_param) { :MERGED_AT_DESC }
let(:first_param) { 2 }
let(:expected_results) do
@@ -291,7 +281,7 @@ RSpec.describe 'getting merge request listings nested in a project' do
merge_request_c,
merge_request_e,
merge_request_a
- ].map(&:to_gid).map(&:to_s)
+ ].map { |mr| global_id_of(mr) }
end
before do
@@ -304,33 +294,6 @@ RSpec.describe 'getting merge request listings nested in a project' do
merge_request_b.metrics.update!(merged_at: 1.day.ago)
end
-
- context 'when paginating backwards' do
- let(:params) { 'first: 2, sort: MERGED_AT_DESC' }
- let(:page_info) { 'pageInfo { startCursor endCursor }' }
-
- before do
- post_graphql(pagination_query(params, page_info), current_user: current_user)
- end
-
- it 'paginates backwards correctly' do
- # first page
- first_page_response_data = graphql_dig_at(Gitlab::Json.parse(response.body), :data, *data_path, :edges)
- end_cursor = graphql_dig_at(Gitlab::Json.parse(response.body), :data, :project, :mergeRequests, :pageInfo, :endCursor)
-
- # second page
- params = "first: 2, after: \"#{end_cursor}\", sort: MERGED_AT_DESC"
- post_graphql(pagination_query(params, page_info), current_user: current_user)
- start_cursor = graphql_dig_at(Gitlab::Json.parse(response.body), :data, :project, :mergeRequests, :pageInfo, :start_cursor)
-
- # going back to the first page
-
- params = "last: 2, before: \"#{start_cursor}\", sort: MERGED_AT_DESC"
- post_graphql(pagination_query(params, page_info), current_user: current_user)
- backward_paginated_response_data = graphql_dig_at(Gitlab::Json.parse(response.body), :data, *data_path, :edges)
- expect(first_page_response_data).to eq(backward_paginated_response_data)
- end
- end
end
end
end
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index fef0e7e160c..6179b43629b 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -5,12 +5,12 @@ require 'spec_helper'
RSpec.describe 'getting pipeline information nested in a project' do
include GraphqlHelpers
- let(:project) { create(:project, :repository, :public) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
- let(:current_user) { create(:user) }
+ let!(:project) { create(:project, :repository, :public) }
+ let!(:pipeline) { create(:ci_pipeline, project: project) }
+ let!(:current_user) { create(:user) }
let(:pipeline_graphql_data) { graphql_data['project']['pipeline'] }
- let(:query) do
+ let!(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
@@ -35,4 +35,45 @@ RSpec.describe 'getting pipeline information nested in a project' do
expect(pipeline_graphql_data.dig('configSource')).to eq('UNKNOWN_SOURCE')
end
+
+ context 'batching' do
+ let!(:pipeline2) { create(:ci_pipeline, project: project, user: current_user, builds: [create(:ci_build, :success)]) }
+ let!(:pipeline3) { create(:ci_pipeline, project: project, user: current_user, builds: [create(:ci_build, :success)]) }
+ let!(:query) { build_query_to_find_pipeline_shas(pipeline, pipeline2, pipeline3) }
+
+ it 'executes the finder once' do
+ mock = double(Ci::PipelinesFinder)
+ opts = { iids: [pipeline.iid, pipeline2.iid, pipeline3.iid].map(&:to_s) }
+
+ expect(Ci::PipelinesFinder).to receive(:new).once.with(project, current_user, opts).and_return(mock)
+ expect(mock).to receive(:execute).once.and_return(Ci::Pipeline.none)
+
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'keeps the queries under the threshold' do
+ control = ActiveRecord::QueryRecorder.new do
+ single_pipeline_query = build_query_to_find_pipeline_shas(pipeline)
+
+ post_graphql(single_pipeline_query, current_user: current_user)
+ end
+
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:success)
+ expect do
+ post_graphql(query, current_user: current_user)
+ end.not_to exceed_query_limit(control)
+ end
+ end
+ end
+
+ private
+
+ def build_query_to_find_pipeline_shas(*pipelines)
+ pipeline_fields = pipelines.map.each_with_index do |pipeline, idx|
+ "pipeline#{idx}: pipeline(iid: \"#{pipeline.iid}\") { sha }"
+ end.join(' ')
+
+ graphql_query_for('project', { 'fullPath' => project.full_path }, pipeline_fields)
+ end
end
diff --git a/spec/requests/api/graphql/project/project_members_spec.rb b/spec/requests/api/graphql/project/project_members_spec.rb
new file mode 100644
index 00000000000..cb937432ef7
--- /dev/null
+++ b/spec/requests/api/graphql/project/project_members_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting project members information' do
+ include GraphqlHelpers
+
+ let_it_be(:parent_group) { create(:group, :public) }
+ let_it_be(:parent_project) { create(:project, :public, group: parent_group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:user_1) { create(:user, username: 'user') }
+ let_it_be(:user_2) { create(:user, username: 'test') }
+
+ let(:member_data) { graphql_data['project']['projectMembers']['edges'] }
+
+ before_all do
+ [user_1, user_2].each { |user| parent_group.add_guest(user) }
+ end
+
+ context 'when the request is correct' do
+ it_behaves_like 'a working graphql query' do
+ before_all do
+ fetch_members(project: parent_project)
+ end
+ end
+
+ it 'returns project members successfully' do
+ fetch_members(project: parent_project)
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(user_1, user_2)
+ end
+
+ it 'returns members that match the search query' do
+ fetch_members(project: parent_project, args: { search: 'test' })
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(user_2)
+ end
+ end
+
+ context 'member relations' do
+ let_it_be(:child_group) { create(:group, :public, parent: parent_group) }
+ let_it_be(:child_project) { create(:project, :public, group: child_group) }
+ let_it_be(:invited_group) { create(:group, :public) }
+ let_it_be(:child_user) { create(:user) }
+ let_it_be(:invited_user) { create(:user) }
+ let_it_be(:group_link) { create(:project_group_link, project: child_project, group: invited_group) }
+
+ before_all do
+ child_project.add_guest(child_user)
+ invited_group.add_guest(invited_user)
+ end
+
+ it 'returns direct members' do
+ fetch_members(project: child_project, args: { relations: [:DIRECT] })
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(child_user)
+ end
+
+ it 'returns invited members plus inherited members' do
+ fetch_members(project: child_project, args: { relations: [:INVITED_GROUPS] })
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(invited_user, user_1, user_2)
+ end
+
+ it 'returns direct, inherited, descendant, and invited members' do
+ fetch_members(project: child_project, args: { relations: [:DIRECT, :INHERITED, :DESCENDANTS, :INVITED_GROUPS] })
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(child_user, user_1, user_2, invited_user)
+ end
+
+ it 'returns an error for an invalid member relation' do
+ fetch_members(project: child_project, args: { relations: [:OBLIQUE] })
+
+ expect(graphql_errors.first)
+ .to include('path' => %w[query project projectMembers relations],
+ 'message' => a_string_including('invalid value ([OBLIQUE])'))
+ end
+ end
+
+ context 'when unauthenticated' do
+ it 'returns members' do
+ fetch_members(current_user: nil, project: parent_project)
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(user_1, user_2)
+ end
+ end
+
+ def fetch_members(project:, current_user: user, args: {})
+ post_graphql(members_query(project.full_path, args), current_user: current_user)
+ end
+
+ def members_query(group_path, args = {})
+ members_node = <<~NODE
+ edges {
+ node {
+ user {
+ id
+ }
+ }
+ }
+ NODE
+
+ graphql_query_for('project',
+ { full_path: group_path },
+ [query_graphql_field('projectMembers', args, members_node)]
+ )
+ end
+
+ def expect_array_response(*items)
+ expect(response).to have_gitlab_http_status(:success)
+ expect(member_data).to be_an Array
+ expect(member_data.map { |node| node['node']['user']['id'] })
+ .to match_array(items.map { |u| global_id_of(u) })
+ end
+end
diff --git a/spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb b/spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb
new file mode 100644
index 00000000000..0f495f3e671
--- /dev/null
+++ b/spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'rendering project pipeline statistics' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ let(:fields) do
+ <<~QUERY
+ weekPipelinesTotals
+ weekPipelinesLabels
+ monthPipelinesLabels
+ monthPipelinesTotals
+ yearPipelinesLabels
+ yearPipelinesTotals
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for('project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('pipelineAnalytics', {}, fields))
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: user)
+ end
+ end
+
+ it "contains two arrays of 8 elements each for the week pipelines" do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data_at(:project, :pipelineAnalytics, :weekPipelinesTotals).length).to eq(8)
+ expect(graphql_data_at(:project, :pipelineAnalytics, :weekPipelinesLabels).length).to eq(8)
+ end
+
+ it "contains two arrays of 31 elements each for the month pipelines" do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data_at(:project, :pipelineAnalytics, :monthPipelinesTotals).length).to eq(31)
+ expect(graphql_data_at(:project, :pipelineAnalytics, :monthPipelinesLabels).length).to eq(31)
+ end
+
+ it "contains two arrays of 13 elements each for the year pipelines" do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data_at(:project, :pipelineAnalytics, :yearPipelinesTotals).length).to eq(13)
+ expect(graphql_data_at(:project, :pipelineAnalytics, :yearPipelinesLabels).length).to eq(13)
+ end
+end
diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb
index 57dbe258ce4..99b15ff00b1 100644
--- a/spec/requests/api/graphql/project/release_spec.rb
+++ b/spec/requests/api/graphql/project/release_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let(:path) { path_prefix }
let(:release_fields) do
- query_graphql_field(%{
+ %{
tagName
tagPath
description
@@ -45,7 +45,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
createdAt
releasedAt
upcomingRelease
- })
+ }
end
before do
@@ -233,7 +233,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let(:path) { path_prefix }
let(:release_fields) do
- query_graphql_field('description')
+ 'description'
end
before do
@@ -394,10 +394,10 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let(:current_user) { developer }
let(:release_fields) do
- query_graphql_field(%{
+ %{
releasedAt
upcomingRelease
- })
+ }
end
before do
diff --git a/spec/requests/api/graphql/project/terraform/states_spec.rb b/spec/requests/api/graphql/project/terraform/states_spec.rb
index 8b67b549efa..2879530acc5 100644
--- a/spec/requests/api/graphql/project/terraform/states_spec.rb
+++ b/spec/requests/api/graphql/project/terraform/states_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'query terraform states' do
include GraphqlHelpers
+ include ::API::Helpers::RelatedResourcesHelpers
let_it_be(:project) { create(:project) }
let_it_be(:terraform_state) { create(:terraform_state, :with_version, :locked, project: project) }
@@ -23,6 +24,8 @@ RSpec.describe 'query terraform states' do
latestVersion {
id
+ downloadPath
+ serial
createdAt
updatedAt
@@ -50,22 +53,32 @@ RSpec.describe 'query terraform states' do
post_graphql(query, current_user: current_user)
end
- it 'returns terraform state data', :aggregate_failures do
- state = data.dig('nodes', 0)
- version = state['latestVersion']
-
- expect(state['id']).to eq(terraform_state.to_global_id.to_s)
- expect(state['name']).to eq(terraform_state.name)
- expect(state['lockedAt']).to eq(terraform_state.locked_at.iso8601)
- expect(state['createdAt']).to eq(terraform_state.created_at.iso8601)
- expect(state['updatedAt']).to eq(terraform_state.updated_at.iso8601)
- expect(state.dig('lockedByUser', 'id')).to eq(terraform_state.locked_by_user.to_global_id.to_s)
-
- expect(version['id']).to eq(latest_version.to_global_id.to_s)
- expect(version['createdAt']).to eq(latest_version.created_at.iso8601)
- expect(version['updatedAt']).to eq(latest_version.updated_at.iso8601)
- expect(version.dig('createdByUser', 'id')).to eq(latest_version.created_by_user.to_global_id.to_s)
- expect(version.dig('job', 'name')).to eq(latest_version.build.name)
+ it 'returns terraform state data' do
+ download_path = expose_path(
+ api_v4_projects_terraform_state_versions_path(
+ id: project.id,
+ name: terraform_state.name,
+ serial: latest_version.version
+ )
+ )
+
+ expect(data['nodes']).to contain_exactly({
+ 'id' => global_id_of(terraform_state),
+ 'name' => terraform_state.name,
+ 'lockedAt' => terraform_state.locked_at.iso8601,
+ 'createdAt' => terraform_state.created_at.iso8601,
+ 'updatedAt' => terraform_state.updated_at.iso8601,
+ 'lockedByUser' => { 'id' => global_id_of(terraform_state.locked_by_user) },
+ 'latestVersion' => {
+ 'id' => eq(global_id_of(latest_version)),
+ 'serial' => eq(latest_version.version),
+ 'downloadPath' => eq(download_path),
+ 'createdAt' => eq(latest_version.created_at.iso8601),
+ 'updatedAt' => eq(latest_version.updated_at.iso8601),
+ 'createdByUser' => { 'id' => eq(global_id_of(latest_version.created_by_user)) },
+ 'job' => { 'name' => eq(latest_version.build.name) }
+ }
+ })
end
it 'returns count of terraform states' do
diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb
index 4b8ffb0675c..b29f9ae913f 100644
--- a/spec/requests/api/graphql/project_query_spec.rb
+++ b/spec/requests/api/graphql/project_query_spec.rb
@@ -5,36 +5,34 @@ require 'spec_helper'
RSpec.describe 'getting project information' do
include GraphqlHelpers
- let(:group) { create(:group) }
- let(:project) { create(:project, :repository, group: group) }
- let(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :repository, group: group) }
+ let_it_be(:current_user) { create(:user) }
+ let(:fields) { all_graphql_fields_for(Project, max_depth: 2, excluded: %w(jiraImports services)) }
let(:query) do
- graphql_query_for(
- 'project',
- { 'fullPath' => project.full_path },
- all_graphql_fields_for('project'.to_s.classify, excluded: %w(jiraImports services))
- )
+ graphql_query_for(:project, { full_path: project.full_path }, fields)
end
context 'when the user has full access to the project' do
let(:full_access_query) do
- graphql_query_for('project', 'fullPath' => project.full_path)
+ graphql_query_for(:project, { full_path: project.full_path },
+ all_graphql_fields_for('Project', max_depth: 2))
end
before do
project.add_maintainer(current_user)
end
- it 'includes the project' do
- post_graphql(query, current_user: current_user)
+ it 'includes the project', :use_clean_rails_memory_store_caching, :request_store do
+ post_graphql(full_access_query, current_user: current_user)
expect(graphql_data['project']).not_to be_nil
end
end
- context 'when the user has access to the project' do
- before do
+ context 'when the user has access to the project', :use_clean_rails_memory_store_caching, :request_store do
+ before_all do
project.add_developer(current_user)
end
@@ -55,10 +53,12 @@ RSpec.describe 'getting project information' do
create(:ci_pipeline, project: project)
end
+ let(:fields) { query_nodes(:pipelines) }
+
it 'is included in the pipelines connection' do
post_graphql(query, current_user: current_user)
- expect(graphql_data['project']['pipelines']['edges'].size).to eq(1)
+ expect(graphql_data_at(:project, :pipelines, :nodes)).to contain_exactly(a_kind_of(Hash))
end
end
@@ -109,7 +109,7 @@ RSpec.describe 'getting project information' do
end
describe 'performance' do
- before do
+ before_all do
project.add_developer(current_user)
mrs = create_list(:merge_request, 10, :closed, :with_head_pipeline,
source_project: project,
@@ -151,8 +151,9 @@ RSpec.describe 'getting project information' do
)))
end
- it 'can lookahead to eliminate N+1 queries', :use_clean_rails_memory_store_caching, :request_store do
- expect { run_query(10) }.to issue_same_number_of_queries_as { run_query(1) }.or_fewer.ignoring_cached_queries
+ it 'can lookahead to eliminate N+1 queries' do
+ baseline = ActiveRecord::QueryRecorder.new { run_query(1) }
+ expect { run_query(10) }.not_to exceed_query_limit(baseline)
end
end
diff --git a/spec/requests/api/graphql/user_query_spec.rb b/spec/requests/api/graphql/user_query_spec.rb
index 8c45a67cb0f..60520906e87 100644
--- a/spec/requests/api/graphql/user_query_spec.rb
+++ b/spec/requests/api/graphql/user_query_spec.rb
@@ -58,9 +58,25 @@ RSpec.describe 'getting user information' do
source_project: project_b, author: user)
end
+ let_it_be(:reviewed_mr) do
+ create(:merge_request, :unique_branches, :unique_author,
+ source_project: project_a, reviewers: [user])
+ end
+
+ let_it_be(:reviewed_mr_b) do
+ create(:merge_request, :unique_branches, :unique_author,
+ source_project: project_b, reviewers: [user])
+ end
+
+ let_it_be(:reviewed_mr_c) do
+ create(:merge_request, :unique_branches, :unique_author,
+ source_project: project_b, reviewers: [user])
+ end
+
let(:current_user) { authorised_user }
let(:authored_mrs) { graphql_data_at(:user, :authored_merge_requests, :nodes) }
let(:assigned_mrs) { graphql_data_at(:user, :assigned_merge_requests, :nodes) }
+ let(:reviewed_mrs) { graphql_data_at(:user, :review_requested_merge_requests, :nodes) }
let(:user_params) { { username: user.username } }
before do
@@ -82,7 +98,8 @@ RSpec.describe 'getting user information' do
'username' => presenter.username,
'webUrl' => presenter.web_url,
'avatarUrl' => presenter.avatar_url,
- 'email' => presenter.public_email
+ 'email' => presenter.public_email,
+ 'publicEmail' => presenter.public_email
))
expect(graphql_data['user']['status']).to match(
@@ -156,6 +173,23 @@ RSpec.describe 'getting user information' do
)
end
end
+
+ context 'filtering by reviewer' do
+ let(:reviewer) { create(:user) }
+ let(:mr_args) { { reviewer_username: reviewer.username } }
+
+ it 'finds the assigned mrs' do
+ assigned_mr_b.reviewers << reviewer
+ assigned_mr_c.reviewers << reviewer
+
+ post_graphql(query, current_user: current_user)
+
+ expect(assigned_mrs).to contain_exactly(
+ a_hash_including('id' => global_id_of(assigned_mr_b)),
+ a_hash_including('id' => global_id_of(assigned_mr_c))
+ )
+ end
+ end
end
context 'the current user does not have access' do
@@ -167,6 +201,95 @@ RSpec.describe 'getting user information' do
end
end
+ describe 'reviewRequestedMergeRequests' do
+ let(:user_fields) do
+ query_graphql_field(:review_requested_merge_requests, mr_args, 'nodes { id }')
+ end
+
+ let(:mr_args) { nil }
+
+ it_behaves_like 'a working graphql query'
+
+ it 'can be found' do
+ expect(reviewed_mrs).to contain_exactly(
+ a_hash_including('id' => global_id_of(reviewed_mr)),
+ a_hash_including('id' => global_id_of(reviewed_mr_b)),
+ a_hash_including('id' => global_id_of(reviewed_mr_c))
+ )
+ end
+
+ context 'applying filters' do
+ context 'filtering by IID without specifying a project' do
+ let(:mr_args) do
+ { iids: [reviewed_mr_b.iid.to_s] }
+ end
+
+ it 'return an argument error that mentions the missing fields' do
+ expect_graphql_errors_to_include(/projectPath/)
+ end
+ end
+
+ context 'filtering by project path and IID' do
+ let(:mr_args) do
+ { project_path: project_b.full_path, iids: [reviewed_mr_b.iid.to_s] }
+ end
+
+ it 'selects the correct MRs' do
+ expect(reviewed_mrs).to contain_exactly(
+ a_hash_including('id' => global_id_of(reviewed_mr_b))
+ )
+ end
+ end
+
+ context 'filtering by project path' do
+ let(:mr_args) do
+ { project_path: project_b.full_path }
+ end
+
+ it 'selects the correct MRs' do
+ expect(reviewed_mrs).to contain_exactly(
+ a_hash_including('id' => global_id_of(reviewed_mr_b)),
+ a_hash_including('id' => global_id_of(reviewed_mr_c))
+ )
+ end
+ end
+
+ context 'filtering by author' do
+ let(:author) { reviewed_mr_b.author }
+ let(:mr_args) { { author_username: author.username } }
+
+ it 'finds the authored mrs' do
+ expect(reviewed_mrs).to contain_exactly(
+ a_hash_including('id' => global_id_of(reviewed_mr_b))
+ )
+ end
+ end
+
+ context 'filtering by assignee' do
+ let(:assignee) { create(:user) }
+ let(:mr_args) { { assignee_username: assignee.username } }
+
+ it 'finds the authored mrs' do
+ reviewed_mr_c.assignees << assignee
+
+ post_graphql(query, current_user: current_user)
+
+ expect(reviewed_mrs).to contain_exactly(
+ a_hash_including('id' => global_id_of(reviewed_mr_c))
+ )
+ end
+ end
+ end
+
+ context 'the current user does not have access' do
+ let(:current_user) { unauthorized_user }
+
+ it 'cannot be found' do
+ expect(reviewed_mrs).to be_empty
+ end
+ end
+ end
+
describe 'authoredMergeRequests' do
let(:user_fields) do
query_graphql_field(:authored_merge_requests, mr_args, 'nodes { id }')
@@ -212,6 +335,23 @@ RSpec.describe 'getting user information' do
end
end
+ context 'filtering by reviewer' do
+ let(:reviewer) { create(:user) }
+ let(:mr_args) { { reviewer_username: reviewer.username } }
+
+ it 'finds the assigned mrs' do
+ authored_mr_b.reviewers << reviewer
+ authored_mr_c.reviewers << reviewer
+
+ post_graphql(query, current_user: current_user)
+
+ expect(authored_mrs).to contain_exactly(
+ a_hash_including('id' => global_id_of(authored_mr_b)),
+ a_hash_including('id' => global_id_of(authored_mr_c))
+ )
+ end
+ end
+
context 'filtering by project path and IID' do
let(:mr_args) do
{ project_path: project_b.full_path, iids: [authored_mr_b.iid.to_s] }
diff --git a/spec/requests/api/graphql/users_spec.rb b/spec/requests/api/graphql/users_spec.rb
index 91ac206676b..72d86c10df1 100644
--- a/spec/requests/api/graphql/users_spec.rb
+++ b/spec/requests/api/graphql/users_spec.rb
@@ -59,20 +59,16 @@ RSpec.describe 'Users' do
describe 'sorting and pagination' do
let_it_be(:data_path) { [:users] }
- def pagination_query(params, page_info)
- graphql_query_for("users", params, "#{page_info} edges { node { id } }")
- end
-
- def pagination_results_data(data)
- data.map { |user| user.dig('node', 'id') }
+ def pagination_query(params)
+ graphql_query_for(:users, params, "#{page_info} nodes { id }")
end
context 'when sorting by created_at' do
- let_it_be(:ascending_users) { [user3, user2, user1, current_user].map(&:to_global_id).map(&:to_s) }
+ let_it_be(:ascending_users) { [user3, user2, user1, current_user].map { |u| global_id_of(u) } }
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'created_asc' }
+ let(:sort_param) { :CREATED_ASC }
let(:first_param) { 1 }
let(:expected_results) { ascending_users }
end
@@ -80,7 +76,7 @@ RSpec.describe 'Users' do
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { 'created_desc' }
+ let(:sort_param) { :CREATED_DESC }
let(:first_param) { 1 }
let(:expected_results) { ascending_users.reverse }
end
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index 5dc8edb87e9..4eaf57a7d35 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -105,7 +105,7 @@ RSpec.describe 'GraphQL' do
stub_authentication_activity_metrics(debug: false)
end
- it 'Authenticates users with a PAT' do
+ it 'authenticates users with a PAT' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter)
diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb
index eb21ae9468c..f65f9384efa 100644
--- a/spec/requests/api/group_clusters_spec.rb
+++ b/spec/requests/api/group_clusters_spec.rb
@@ -89,6 +89,8 @@ RSpec.describe API::GroupClusters do
expect(json_response['environment_scope']).to eq('*')
expect(json_response['cluster_type']).to eq('group_type')
expect(json_response['domain']).to eq('example.com')
+ expect(json_response['enabled']).to be_truthy
+ expect(json_response['managed']).to be_truthy
end
it 'returns group information' do
@@ -172,6 +174,7 @@ RSpec.describe API::GroupClusters do
name: 'test-cluster',
domain: 'domain.example.com',
managed: false,
+ enabled: false,
namespace_per_environment: false,
platform_kubernetes_attributes: platform_kubernetes_attributes,
management_project_id: management_project_id
@@ -206,6 +209,7 @@ RSpec.describe API::GroupClusters do
expect(cluster_result.name).to eq('test-cluster')
expect(cluster_result.domain).to eq('domain.example.com')
expect(cluster_result.managed).to be_falsy
+ expect(cluster_result.enabled).to be_falsy
expect(cluster_result.management_project_id).to eq management_project_id
expect(cluster_result.namespace_per_environment).to eq(false)
expect(platform_kubernetes.rbac?).to be_truthy
@@ -342,7 +346,9 @@ RSpec.describe API::GroupClusters do
{
domain: domain,
platform_kubernetes_attributes: platform_kubernetes_attributes,
- management_project_id: management_project_id
+ management_project_id: management_project_id,
+ managed: false,
+ enabled: false
}
end
@@ -381,6 +387,8 @@ RSpec.describe API::GroupClusters do
it 'updates cluster attributes' do
expect(cluster.domain).to eq('new-domain.com')
expect(cluster.management_project).to eq(management_project)
+ expect(cluster.managed).to be_falsy
+ expect(cluster.enabled).to be_falsy
end
end
@@ -394,6 +402,8 @@ RSpec.describe API::GroupClusters do
it 'does not update cluster attributes' do
expect(cluster.domain).to eq('old-domain.com')
expect(cluster.management_project).to be_nil
+ expect(cluster.managed).to be_truthy
+ expect(cluster.enabled).to be_truthy
end
it 'returns validation errors' do
diff --git a/spec/requests/api/group_import_spec.rb b/spec/requests/api/group_import_spec.rb
index cb63206fcb8..d8e945baf6a 100644
--- a/spec/requests/api/group_import_spec.rb
+++ b/spec/requests/api/group_import_spec.rb
@@ -264,7 +264,7 @@ RSpec.describe API::GroupImport do
subject
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it 'rejects requests that bypassed gitlab-workhorse' do
@@ -285,7 +285,7 @@ RSpec.describe API::GroupImport do
subject
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response).not_to have_key('TempPath')
expect(json_response['RemoteObject']).to have_key('ID')
expect(json_response['RemoteObject']).to have_key('GetURL')
@@ -304,7 +304,7 @@ RSpec.describe API::GroupImport do
subject
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response['TempPath']).to eq(ImportExportUploader.workhorse_local_upload_path)
expect(json_response['RemoteObject']).to be_nil
end
diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb
index 5bbcb0c1950..d5fed330401 100644
--- a/spec/requests/api/import_github_spec.rb
+++ b/spec/requests/api/import_github_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe API::ImportGithub do
it 'returns 201 response when the project is imported successfully' do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post api("/import/github", user), params: {
@@ -59,7 +59,7 @@ RSpec.describe API::ImportGithub do
it 'returns 201 response when the project is imported successfully from GHE' do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post api("/import/github", user), params: {
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index 6fe77727702..e04f63befd0 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -220,6 +220,8 @@ RSpec.describe API::Internal::Base do
end
it 'returns a token without expiry when the expires_at parameter is missing' do
+ token_size = (PersonalAccessToken.token_prefix || '').size + 20
+
post api('/internal/personal_access_token'),
params: {
secret_token: secret_token,
@@ -229,12 +231,14 @@ RSpec.describe API::Internal::Base do
}
expect(json_response['success']).to be_truthy
- expect(json_response['token']).to match(/\A\S{20}\z/)
+ expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
expect(json_response['scopes']).to match_array(%w(read_api read_repository))
expect(json_response['expires_at']).to be_nil
end
it 'returns a token with expiry when it receives a valid expires_at parameter' do
+ token_size = (PersonalAccessToken.token_prefix || '').size + 20
+
post api('/internal/personal_access_token'),
params: {
secret_token: secret_token,
@@ -245,7 +249,7 @@ RSpec.describe API::Internal::Base do
}
expect(json_response['success']).to be_truthy
- expect(json_response['token']).to match(/\A\S{20}\z/)
+ expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
expect(json_response['scopes']).to match_array(%w(read_api read_repository))
expect(json_response['expires_at']).to eq('9001-11-17')
end
@@ -722,8 +726,7 @@ RSpec.describe API::Internal::Base do
'ssh',
{
authentication_abilities: [:read_project, :download_code, :push_code],
- namespace_path: project.namespace.path,
- repository_path: project.path,
+ repository_path: "#{project.full_path}.git",
redirected_path: nil
}
).and_return(access_checker)
@@ -1337,9 +1340,13 @@ RSpec.describe API::Internal::Base do
end
context 'when the OTP is valid' do
- it 'returns success' do
+ it 'registers a new OTP session and returns success' do
allow_any_instance_of(Users::ValidateOtpService).to receive(:execute).with(otp).and_return(status: :success)
+ expect_next_instance_of(::Gitlab::Auth::Otp::SessionEnforcer) do |session_enforcer|
+ expect(session_enforcer).to receive(:update_session).once
+ end
+
subject
expect(json_response['success']).to be_truthy
diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb
index a532b8e59f2..afff3647b91 100644
--- a/spec/requests/api/internal/kubernetes_spec.rb
+++ b/spec/requests/api/internal/kubernetes_spec.rb
@@ -87,7 +87,7 @@ RSpec.describe API::Internal::Kubernetes do
end
end
- describe "GET /internal/kubernetes/agent_info" do
+ describe 'GET /internal/kubernetes/agent_info' do
def send_request(headers: {}, params: {})
get api('/internal/kubernetes/agent_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
end
@@ -137,9 +137,7 @@ RSpec.describe API::Internal::Kubernetes do
include_examples 'agent authentication'
context 'an agent is found' do
- let!(:agent_token) { create(:cluster_agent_token) }
-
- let(:agent) { agent_token.agent }
+ let_it_be(:agent_token) { create(:cluster_agent_token) }
context 'project is public' do
let(:project) { create(:project, :public) }
@@ -186,6 +184,16 @@ RSpec.describe API::Internal::Kubernetes do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'and agent belongs to project' do
+ let(:agent_token) { create(:cluster_agent_token, agent: create(:cluster_agent, project: project)) }
+
+ it 'returns 200' do
+ send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+ end
end
context 'project is internal' do
diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb
index 9a63e2a8ed5..5b970ca605c 100644
--- a/spec/requests/api/internal/pages_spec.rb
+++ b/spec/requests/api/internal/pages_spec.rb
@@ -128,32 +128,38 @@ RSpec.describe API::Internal::Pages do
)
end
- it 'responds with proxy configuration' do
+ it 'responds with 204 because of feature deprecation' do
query_host(serverless_domain.uri.host)
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('internal/serverless/virtual_domain')
-
- expect(json_response['certificate']).to eq(pages_domain.certificate)
- expect(json_response['key']).to eq(pages_domain.key)
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(response.body).to be_empty
- expect(json_response['lookup_paths']).to eq(
- [
- {
- 'source' => {
- 'type' => 'serverless',
- 'service' => "test-function.#{project.name}-#{project.id}-#{environment.slug}.#{serverless_domain_cluster.knative.hostname}",
- 'cluster' => {
- 'hostname' => serverless_domain_cluster.knative.hostname,
- 'address' => serverless_domain_cluster.knative.external_ip,
- 'port' => 443,
- 'cert' => serverless_domain_cluster.certificate,
- 'key' => serverless_domain_cluster.key
- }
- }
- }
- ]
- )
+ ##
+ # Serverless serving and reverse proxy to Kubernetes / Knative has
+ # been deprecated and disabled, as per
+ # https://gitlab.com/gitlab-org/gitlab-pages/-/issues/467
+ #
+ # expect(response).to match_response_schema('internal/serverless/virtual_domain')
+ # expect(json_response['certificate']).to eq(pages_domain.certificate)
+ # expect(json_response['key']).to eq(pages_domain.key)
+ #
+ # expect(json_response['lookup_paths']).to eq(
+ # [
+ # {
+ # 'source' => {
+ # 'type' => 'serverless',
+ # 'service' => "test-function.#{project.name}-#{project.id}-#{environment.slug}.#{serverless_domain_cluster.knative.hostname}",
+ # 'cluster' => {
+ # 'hostname' => serverless_domain_cluster.knative.hostname,
+ # 'address' => serverless_domain_cluster.knative.external_ip,
+ # 'port' => 443,
+ # 'cert' => serverless_domain_cluster.certificate,
+ # 'key' => serverless_domain_cluster.key
+ # }
+ # }
+ # }
+ # ]
+ # )
end
end
end
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index c1498e03f76..f8521818845 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -3,6 +3,7 @@
require 'spec_helper'
RSpec.describe API::Jobs do
+ using RSpec::Parameterized::TableSyntax
include HttpIOHelpers
let_it_be(:project, reload: true) do
@@ -717,6 +718,58 @@ RSpec.describe API::Jobs do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
+
+ context 'when ci_debug_trace is set to true' do
+ before_all do
+ create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: true)
+ end
+
+ where(:public_builds, :user_project_role, :expected_status) do
+ true | 'developer' | :ok
+ true | 'guest' | :forbidden
+ false | 'developer' | :ok
+ false | 'guest' | :forbidden
+ end
+
+ with_them do
+ before do
+ project.update!(public_builds: public_builds)
+ project.add_role(user, user_project_role)
+
+ get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user)
+ end
+
+ it 'renders trace to authorized users' do
+ expect(response).to have_gitlab_http_status(expected_status)
+ end
+ end
+
+ context 'with restrict_access_to_build_debug_mode feature disabled' do
+ before do
+ stub_feature_flags(restrict_access_to_build_debug_mode: false)
+ end
+
+ where(:public_builds, :user_project_role, :expected_status) do
+ true | 'developer' | :ok
+ true | 'guest' | :ok
+ false | 'developer' | :ok
+ false | 'guest' | :forbidden
+ end
+
+ with_them do
+ before do
+ project.update!(public_builds: public_builds)
+ project.add_role(user, user_project_role)
+
+ get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user)
+ end
+
+ it 'renders trace to authorized users' do
+ expect(response).to have_gitlab_http_status(expected_status)
+ end
+ end
+ end
+ end
end
describe 'POST /projects/:id/jobs/:job_id/cancel' do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e7005bd3ec5..4339f1dd830 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1888,6 +1888,54 @@ RSpec.describe API::MergeRequests do
expect(response).to have_gitlab_http_status(:created)
end
end
+
+ describe 'SSE counter' do
+ let(:headers) { {} }
+ let(:params) do
+ {
+ title: 'Test merge_request',
+ source_branch: 'feature_conflict',
+ target_branch: 'master',
+ author_id: user.id,
+ milestone_id: milestone.id,
+ squash: true
+ }
+ end
+
+ subject { post api("/projects/#{project.id}/merge_requests", user), params: params, headers: headers }
+
+ it 'does not increase the SSE counter by default' do
+ expect(Gitlab::UsageDataCounters::EditorUniqueCounter).not_to receive(:track_sse_edit_action)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ context 'when referer is not the SSE' do
+ let(:headers) { { 'HTTP_REFERER' => 'https://gitlab.com' } }
+
+ it 'does not increase the SSE counter by default' do
+ expect(Gitlab::UsageDataCounters::EditorUniqueCounter).not_to receive(:track_sse_edit_action)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when referer is the SSE' do
+ let(:headers) { { 'HTTP_REFERER' => project_show_sse_url(project, 'master/README.md') } }
+
+ it 'increases the SSE counter by default' do
+ expect(Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_sse_edit_action).with(author: user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+ end
end
describe 'PUT /projects/:id/merge_reuests/:merge_request_iid' do
diff --git a/spec/requests/api/nuget_packages_spec.rb b/spec/requests/api/nuget_packages_spec.rb
deleted file mode 100644
index 62f244c433b..00000000000
--- a/spec/requests/api/nuget_packages_spec.rb
+++ /dev/null
@@ -1,533 +0,0 @@
-# frozen_string_literal: true
-require 'spec_helper'
-
-RSpec.describe API::NugetPackages do
- include WorkhorseHelpers
- include PackagesManagerApiSpecHelpers
- include HttpBasicAuthHelpers
-
- let_it_be(:user) { create(:user) }
- let_it_be(:project, reload: true) { create(:project, :public) }
- let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
- let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
- let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
-
- describe 'GET /api/v4/projects/:id/packages/nuget' do
- let(:url) { "/projects/#{project.id}/packages/nuget/index.json" }
-
- subject { get api(url) }
-
- context 'without the need for a license' do
- context 'with valid project' do
- using RSpec::Parameterized::TableSyntax
-
- context 'personal token' do
- where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
- 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
- 'PUBLIC' | :developer | true | false | 'process nuget service index request' | :success
- 'PUBLIC' | :guest | true | false | 'process nuget service index request' | :success
- 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
- 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
- 'PUBLIC' | :developer | false | false | 'process nuget service index request' | :success
- 'PUBLIC' | :guest | false | false | 'process nuget service index request' | :success
- 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
- 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
- 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- end
-
- with_them do
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
- let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
-
- subject { get api(url), headers: headers }
-
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
- end
-
- it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
- end
- end
-
- context 'with job token' do
- where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
- 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
- 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
- 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
- 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
- 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
- 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- end
-
- with_them do
- let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') }
- let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) }
-
- subject { get api(url), headers: headers }
-
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
- end
-
- it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
- end
- end
- end
-
- it_behaves_like 'deploy token for package GET requests'
-
- it_behaves_like 'rejects nuget access with unknown project id'
-
- it_behaves_like 'rejects nuget access with invalid project id'
- end
- end
-
- describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do
- let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
- let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
- let(:url) { "/projects/#{project.id}/packages/nuget/authorize" }
- let(:headers) { {} }
-
- subject { put api(url), headers: headers }
-
- context 'without the need for a license' do
- context 'with valid project' do
- using RSpec::Parameterized::TableSyntax
-
- where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success
- 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
- 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
- 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success
- 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- end
-
- with_them do
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
- let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
- let(:headers) { user_headers.merge(workhorse_header) }
-
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
- end
-
- it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
- end
- end
-
- it_behaves_like 'deploy token for package uploads'
-
- it_behaves_like 'rejects nuget access with unknown project id'
-
- it_behaves_like 'rejects nuget access with invalid project id'
- end
- end
-
- describe 'PUT /api/v4/projects/:id/packages/nuget' do
- let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
- let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
- let_it_be(:file_name) { 'package.nupkg' }
- let(:url) { "/projects/#{project.id}/packages/nuget" }
- let(:headers) { {} }
- let(:params) { { package: temp_file(file_name) } }
- let(:file_key) { :package }
- let(:send_rewritten_field) { true }
-
- subject do
- workhorse_finalize(
- api(url),
- method: :put,
- file_key: file_key,
- params: params,
- headers: headers,
- send_rewritten_field: send_rewritten_field
- )
- end
-
- context 'without the need for a license' do
- context 'with valid project' do
- using RSpec::Parameterized::TableSyntax
-
- where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'process nuget upload' | :created
- 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
- 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
- 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | true | true | 'process nuget upload' | :created
- 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- end
-
- with_them do
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
- let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
- let(:headers) { user_headers.merge(workhorse_header) }
-
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
- end
-
- it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
- end
- end
-
- it_behaves_like 'deploy token for package uploads'
-
- it_behaves_like 'rejects nuget access with unknown project id'
-
- it_behaves_like 'rejects nuget access with invalid project id'
-
- context 'file size above maximum limit' do
- let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
-
- before do
- allow_next_instance_of(UploadedFile) do |uploaded_file|
- allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1)
- end
- end
-
- it_behaves_like 'returning response status', :bad_request
- end
- end
- end
-
- describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
- include_context 'with expected presenters dependency groups'
-
- let_it_be(:package_name) { 'Dummy.Package' }
- let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) }
- let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
- let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" }
-
- subject { get api(url) }
-
- before do
- packages.each { |pkg| create_dependencies_for(pkg) }
- end
-
- context 'without the need for license' do
- context 'with valid project' do
- using RSpec::Parameterized::TableSyntax
-
- where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success
- 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success
- 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name level' | :success
- 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name level' | :success
- 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success
- 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success
- 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name level' | :success
- 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name level' | :success
- 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name level' | :success
- 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success
- 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- end
-
- with_them do
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
- let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
-
- subject { get api(url), headers: headers }
-
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
- end
-
- it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
- end
-
- it_behaves_like 'deploy token for package GET requests'
-
- it_behaves_like 'rejects nuget access with unknown project id'
-
- it_behaves_like 'rejects nuget access with invalid project id'
- end
- end
- end
-
- describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do
- include_context 'with expected presenters dependency groups'
-
- let_it_be(:package_name) { 'Dummy.Package' }
- let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) }
- let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
- let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" }
-
- subject { get api(url) }
-
- before do
- create_dependencies_for(package)
- end
-
- context 'without the need for a license' do
- context 'with valid project' do
- using RSpec::Parameterized::TableSyntax
-
- where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
- 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success
- 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name and package version level' | :success
- 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name and package version level' | :success
- 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success
- 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success
- 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name and package version level' | :success
- 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name and package version level' | :success
- 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name and package version level' | :success
- 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
- 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- end
-
- with_them do
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
- let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
-
- subject { get api(url), headers: headers }
-
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
- end
-
- it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
- end
- end
-
- it_behaves_like 'deploy token for package GET requests'
-
- context 'with invalid package name' do
- let_it_be(:package_name) { 'Unkown' }
-
- it_behaves_like 'rejects nuget packages access', :developer, :not_found
- end
- end
- end
-
- describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do
- let_it_be(:package_name) { 'Dummy.Package' }
- let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) }
- let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.json" }
-
- subject { get api(url) }
-
- context 'without the need for a license' do
- context 'with valid project' do
- using RSpec::Parameterized::TableSyntax
-
- where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success
- 'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success
- 'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success
- 'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success
- 'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success
- 'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success
- 'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success
- 'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success
- 'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success
- 'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :success
- 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- end
-
- with_them do
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
- let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
-
- subject { get api(url), headers: headers }
-
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
- end
-
- it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
- end
- end
-
- it_behaves_like 'deploy token for package GET requests'
-
- it_behaves_like 'rejects nuget access with unknown project id'
-
- it_behaves_like 'rejects nuget access with invalid project id'
- end
- end
-
- describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do
- let_it_be(:package_name) { 'Dummy.Package' }
- let_it_be(:package) { create(:nuget_package, project: project, name: package_name) }
-
- let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" }
-
- subject { get api(url) }
-
- context 'without the need for a license' do
- context 'with valid project' do
- using RSpec::Parameterized::TableSyntax
-
- where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success
- 'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success
- 'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success
- 'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success
- 'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success
- 'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success
- 'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success
- 'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success
- 'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success
- 'PRIVATE' | :developer | true | true | 'process nuget download content request' | :success
- 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- end
-
- with_them do
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
- let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
-
- subject { get api(url), headers: headers }
-
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
- end
-
- it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
- end
- end
-
- it_behaves_like 'deploy token for package GET requests'
-
- it_behaves_like 'rejects nuget access with unknown project id'
-
- it_behaves_like 'rejects nuget access with invalid project id'
- end
- end
-
- describe 'GET /api/v4/projects/:id/packages/nuget/query' do
- let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) }
- let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') }
- let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) }
- let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) }
- let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) }
- let_it_be(:package_e) { create(:nuget_package, name: 'Foo.BarE', project: project) }
- let(:search_term) { 'uMmy' }
- let(:take) { 26 }
- let(:skip) { 0 }
- let(:include_prereleases) { true }
- let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } }
- let(:url) { "/projects/#{project.id}/packages/nuget/query?#{query_parameters.to_query}" }
-
- subject { get api(url) }
-
- context 'without the need for a license' do
- context 'with valid project' do
- using RSpec::Parameterized::TableSyntax
-
- where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'process nuget search request' | :success
- 'PUBLIC' | :guest | true | true | 'process nuget search request' | :success
- 'PUBLIC' | :developer | true | false | 'process nuget search request' | :success
- 'PUBLIC' | :guest | true | false | 'process nuget search request' | :success
- 'PUBLIC' | :developer | false | true | 'process nuget search request' | :success
- 'PUBLIC' | :guest | false | true | 'process nuget search request' | :success
- 'PUBLIC' | :developer | false | false | 'process nuget search request' | :success
- 'PUBLIC' | :guest | false | false | 'process nuget search request' | :success
- 'PUBLIC' | :anonymous | false | true | 'process nuget search request' | :success
- 'PRIVATE' | :developer | true | true | 'process nuget search request' | :success
- 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
- 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
- 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
- end
-
- with_them do
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
- let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
-
- subject { get api(url), headers: headers }
-
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
- end
-
- it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
- end
- end
-
- it_behaves_like 'deploy token for package GET requests'
-
- it_behaves_like 'rejects nuget access with unknown project id'
-
- it_behaves_like 'rejects nuget access with invalid project id'
- end
- end
-end
diff --git a/spec/requests/api/nuget_project_packages_spec.rb b/spec/requests/api/nuget_project_packages_spec.rb
new file mode 100644
index 00000000000..df1daf39144
--- /dev/null
+++ b/spec/requests/api/nuget_project_packages_spec.rb
@@ -0,0 +1,280 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::NugetProjectPackages do
+ include WorkhorseHelpers
+ include PackagesManagerApiSpecHelpers
+ include HttpBasicAuthHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ describe 'GET /api/v4/projects/:id/packages/nuget' do
+ it_behaves_like 'handling nuget service requests' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/index.json" }
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
+ it_behaves_like 'handling nuget metadata requests with package name' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" }
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do
+ it_behaves_like 'handling nuget metadata requests with package name and package version' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" }
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/query' do
+ it_behaves_like 'handling nuget search requests' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/query?#{query_parameters.to_query}" }
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do
+ let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:url) { "/projects/#{project.id}/packages/nuget/authorize" }
+ let(:headers) { {} }
+
+ subject { put api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success
+ 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/nuget' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let_it_be(:file_name) { 'package.nupkg' }
+ let(:url) { "/projects/#{project.id}/packages/nuget" }
+ let(:headers) { {} }
+ let(:params) { { package: temp_file(file_name) } }
+ let(:file_key) { :package }
+ let(:send_rewritten_field) { true }
+
+ subject do
+ workhorse_finalize(
+ api(url),
+ method: :put,
+ file_key: file_key,
+ params: params,
+ headers: headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget upload' | :created
+ 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process nuget upload' | :created
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+
+ context 'file size above maximum limit' do
+ let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
+
+ before do
+ allow_next_instance_of(UploadedFile) do |uploaded_file|
+ allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1)
+ end
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) }
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.json" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:package) { create(:nuget_package, project: project, name: package_name) }
+
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget download content request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+end
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index 7b37862af74..f784f677c25 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -88,6 +88,8 @@ RSpec.describe API::ProjectClusters do
expect(json_response['environment_scope']).to eq('*')
expect(json_response['cluster_type']).to eq('project_type')
expect(json_response['domain']).to eq('example.com')
+ expect(json_response['enabled']).to be_truthy
+ expect(json_response['managed']).to be_truthy
end
it 'returns project information' do
@@ -171,6 +173,7 @@ RSpec.describe API::ProjectClusters do
name: 'test-cluster',
domain: 'domain.example.com',
managed: false,
+ enabled: false,
namespace_per_environment: false,
platform_kubernetes_attributes: platform_kubernetes_attributes,
management_project_id: management_project_id
@@ -202,6 +205,7 @@ RSpec.describe API::ProjectClusters do
expect(cluster_result.name).to eq('test-cluster')
expect(cluster_result.domain).to eq('domain.example.com')
expect(cluster_result.managed).to be_falsy
+ expect(cluster_result.enabled).to be_falsy
expect(cluster_result.management_project_id).to eq management_project_id
expect(cluster_result.namespace_per_environment).to eq(false)
expect(platform_kubernetes.rbac?).to be_truthy
@@ -337,7 +341,9 @@ RSpec.describe API::ProjectClusters do
{
domain: 'new-domain.com',
platform_kubernetes_attributes: platform_kubernetes_attributes,
- management_project_id: management_project_id
+ management_project_id: management_project_id,
+ managed: false,
+ enabled: false
}
end
@@ -373,6 +379,8 @@ RSpec.describe API::ProjectClusters do
it 'updates cluster attributes' do
expect(response).to have_gitlab_http_status(:ok)
expect(cluster.domain).to eq('new-domain.com')
+ expect(cluster.managed).to be_falsy
+ expect(cluster.enabled).to be_falsy
expect(cluster.platform_kubernetes.namespace).to eq('new-namespace')
expect(cluster.management_project).to eq(management_project)
end
@@ -384,6 +392,8 @@ RSpec.describe API::ProjectClusters do
it 'does not update cluster attributes' do
expect(response).to have_gitlab_http_status(:bad_request)
expect(cluster.domain).not_to eq('new_domain.com')
+ expect(cluster.managed).to be_truthy
+ expect(cluster.enabled).to be_truthy
expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace')
expect(cluster.management_project).not_to eq(management_project)
end
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index a6ae636996e..8e99d37c84f 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -303,7 +303,7 @@ RSpec.describe API::ProjectImport do
subject
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response['TempPath']).to eq(ImportExportUploader.workhorse_local_upload_path)
end
@@ -325,7 +325,7 @@ RSpec.describe API::ProjectImport do
subject
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response).not_to have_key('TempPath')
expect(json_response['RemoteObject']).to have_key('ID')
expect(json_response['RemoteObject']).to have_key('GetURL')
@@ -344,7 +344,7 @@ RSpec.describe API::ProjectImport do
subject
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response['TempPath']).to eq(ImportExportUploader.workhorse_local_upload_path)
expect(json_response['RemoteObject']).to be_nil
end
diff --git a/spec/requests/api/project_repository_storage_moves_spec.rb b/spec/requests/api/project_repository_storage_moves_spec.rb
index ecf4c75b52f..15e69c2aa16 100644
--- a/spec/requests/api/project_repository_storage_moves_spec.rb
+++ b/spec/requests/api/project_repository_storage_moves_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe API::ProjectRepositoryStorageMoves do
include AccessMatchersForRequest
let_it_be(:user) { create(:admin) }
- let_it_be(:project) { create(:project) }
- let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, project: project) }
+ let_it_be(:project) { create(:project, :repository).tap { |project| project.track_project_repository } }
+ let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, container: project) }
shared_examples 'get single project repository storage move' do
let(:project_repository_storage_move_id) { storage_move.id }
@@ -63,14 +63,14 @@ RSpec.describe API::ProjectRepositoryStorageMoves do
control = ActiveRecord::QueryRecorder.new { get_project_repository_storage_moves }
- create(:project_repository_storage_move, :scheduled, project: project)
+ create(:project_repository_storage_move, :scheduled, container: project)
expect { get_project_repository_storage_moves }.not_to exceed_query_limit(control)
end
it 'returns the most recently created first' do
- storage_move_oldest = create(:project_repository_storage_move, :scheduled, project: project, created_at: 2.days.ago)
- storage_move_middle = create(:project_repository_storage_move, :scheduled, project: project, created_at: 1.day.ago)
+ storage_move_oldest = create(:project_repository_storage_move, :scheduled, container: project, created_at: 2.days.ago)
+ storage_move_middle = create(:project_repository_storage_move, :scheduled, container: project, created_at: 1.day.ago)
get_project_repository_storage_moves
@@ -159,4 +159,64 @@ RSpec.describe API::ProjectRepositoryStorageMoves do
end
end
end
+
+ describe 'POST /project_repository_storage_moves' do
+ let(:source_storage_name) { 'default' }
+ let(:destination_storage_name) { 'test_second_storage' }
+
+ def create_project_repository_storage_moves
+ post api('/project_repository_storage_moves', user), params: {
+ source_storage_name: source_storage_name,
+ destination_storage_name: destination_storage_name
+ }
+ end
+
+ before do
+ stub_storage_settings('test_second_storage' => { 'path' => 'tmp/tests/extra_storage' })
+ end
+
+ it 'schedules the worker' do
+ expect(ProjectScheduleBulkRepositoryShardMovesWorker).to receive(:perform_async).with(source_storage_name, destination_storage_name)
+
+ create_project_repository_storage_moves
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+
+ context 'source_storage_name is invalid' do
+ let(:destination_storage_name) { 'not-a-real-storage' }
+
+ it 'gives an error' do
+ create_project_repository_storage_moves
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'destination_storage_name is missing' do
+ let(:destination_storage_name) { nil }
+
+ it 'schedules the worker' do
+ expect(ProjectScheduleBulkRepositoryShardMovesWorker).to receive(:perform_async).with(source_storage_name, destination_storage_name)
+
+ create_project_repository_storage_moves
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+ end
+
+ context 'destination_storage_name is invalid' do
+ let(:destination_storage_name) { 'not-a-real-storage' }
+
+ it 'gives an error' do
+ create_project_repository_storage_moves
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ describe 'normal user' do
+ it { expect { create_project_repository_storage_moves }.to be_denied_for(:user) }
+ end
+ end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 234ac1778fd..ad5468fb54c 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -254,7 +254,7 @@ RSpec.describe API::Projects do
statistics = json_response.find { |p| p['id'] == project.id }['statistics']
expect(statistics).to be_present
- expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size')
+ expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size', 'packages_size')
end
it "does not include license by default" do
@@ -619,7 +619,7 @@ RSpec.describe API::Projects do
end
context 'sorting by project statistics' do
- %w(repository_size storage_size wiki_size).each do |order_by|
+ %w(repository_size storage_size wiki_size packages_size).each do |order_by|
context "sorting by #{order_by}" do
before do
ProjectStatistics.update_all(order_by => 100)
@@ -873,6 +873,7 @@ RSpec.describe API::Projects do
jobs_enabled: false,
merge_requests_enabled: false,
forking_access_level: 'disabled',
+ analytics_access_level: 'disabled',
wiki_enabled: false,
resolve_outdated_diff_discussions: false,
remove_source_branch_after_merge: true,
@@ -883,7 +884,9 @@ RSpec.describe API::Projects do
only_allow_merge_if_all_discussions_are_resolved: false,
ci_config_path: 'a/custom/path',
merge_method: 'ff'
- })
+ }).tap do |attrs|
+ attrs[:operations_access_level] = 'disabled'
+ end
post api('/projects', user), params: project
@@ -900,6 +903,8 @@ RSpec.describe API::Projects do
expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.operations_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.analytics_access_level).to eq(ProjectFeature::DISABLED)
end
it 'creates a project using a template' do
@@ -1579,6 +1584,7 @@ RSpec.describe API::Projects do
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
expect(json_response['allow_merge_on_skipped_pipeline']).to eq(project.allow_merge_on_skipped_pipeline)
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
+ expect(json_response['operations_access_level']).to be_present
end
end
@@ -1619,8 +1625,10 @@ RSpec.describe API::Projects do
expect(json_response['issues_access_level']).to be_present
expect(json_response['merge_requests_access_level']).to be_present
expect(json_response['forking_access_level']).to be_present
+ expect(json_response['analytics_access_level']).to be_present
expect(json_response['wiki_access_level']).to be_present
expect(json_response['builds_access_level']).to be_present
+ expect(json_response['operations_access_level']).to be_present
expect(json_response).to have_key('emails_disabled')
expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['remove_source_branch_after_merge']).to be_truthy
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 63ed57c5045..2157e69e7bf 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -76,7 +76,9 @@ RSpec.describe API::Services do
required_attributes = service_attrs_list.select do |attr|
service_klass.validators_on(attr).any? do |v|
- v.class == ActiveRecord::Validations::PresenceValidator
+ v.class == ActiveRecord::Validations::PresenceValidator &&
+ # exclude presence validators with conditional since those are not really required
+ ![:if, :unless].any? { |cond| v.options.include?(cond) }
end
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 03320549e44..8fb0f8fc51a 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -43,6 +43,7 @@ RSpec.describe API::Settings, 'Settings' do
expect(json_response['spam_check_endpoint_url']).to be_nil
expect(json_response['wiki_page_max_content_bytes']).to be_a(Integer)
expect(json_response['require_admin_approval_after_user_signup']).to eq(true)
+ expect(json_response['personal_access_token_prefix']).to be_nil
end
end
@@ -122,7 +123,8 @@ RSpec.describe API::Settings, 'Settings' do
spam_check_endpoint_url: 'https://example.com/spam_check',
disabled_oauth_sign_in_sources: 'unknown',
import_sources: 'github,bitbucket',
- wiki_page_max_content_bytes: 12345
+ wiki_page_max_content_bytes: 12345,
+ personal_access_token_prefix: "GL-"
}
expect(response).to have_gitlab_http_status(:ok)
@@ -166,6 +168,7 @@ RSpec.describe API::Settings, 'Settings' do
expect(json_response['disabled_oauth_sign_in_sources']).to eq([])
expect(json_response['import_sources']).to match_array(%w(github bitbucket))
expect(json_response['wiki_page_max_content_bytes']).to eq(12345)
+ expect(json_response['personal_access_token_prefix']).to eq("GL-")
end
end
@@ -451,5 +454,25 @@ RSpec.describe API::Settings, 'Settings' do
expect(json_response['error']).to eq('spam_check_endpoint_url is missing')
end
end
+
+ context "personal access token prefix settings" do
+ context "handles validation errors" do
+ it "fails to update the settings with too long prefix" do
+ put api("/application/settings", admin), params: { personal_access_token_prefix: "prefix" * 10 }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ message = json_response["message"]
+ expect(message["personal_access_token_prefix"]).to include(a_string_matching("is too long"))
+ end
+
+ it "fails to update the settings with invalid characters in the prefix" do
+ put api("/application/settings", admin), params: { personal_access_token_prefix: "éñ" }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ message = json_response["message"]
+ expect(message["personal_access_token_prefix"]).to include(a_string_matching("can contain only letters of the Base64 alphabet"))
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/usage_data_spec.rb b/spec/requests/api/usage_data_spec.rb
index 4f4f386e9db..d44f179eed8 100644
--- a/spec/requests/api/usage_data_spec.rb
+++ b/spec/requests/api/usage_data_spec.rb
@@ -5,6 +5,87 @@ require 'spec_helper'
RSpec.describe API::UsageData do
let_it_be(:user) { create(:user) }
+ describe 'POST /usage_data/increment_counter' do
+ let(:endpoint) { '/usage_data/increment_counter' }
+ let(:known_event) { "#{known_event_prefix}_#{known_event_postfix}" }
+ let(:known_event_prefix) { "static_site_editor" }
+ let(:known_event_postfix) { 'commits' }
+ let(:unknown_event) { 'unknown' }
+
+ context 'without CSRF token' do
+ it 'returns forbidden' do
+ stub_feature_flags(usage_data_api: true)
+ allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(false)
+
+ post api(endpoint, user), params: { event: known_event }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'usage_data_api feature not enabled' do
+ it 'returns not_found' do
+ stub_feature_flags(usage_data_api: false)
+
+ post api(endpoint, user), params: { event: known_event }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'without authentication' do
+ it 'returns 401 response' do
+ post api(endpoint), params: { event: known_event }
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'with authentication' do
+ before do
+ stub_feature_flags(usage_data_api: true)
+ stub_feature_flags("usage_data_#{known_event}" => true)
+ stub_application_setting(usage_ping_enabled: true)
+ allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(true)
+ end
+
+ context 'when event is missing from params' do
+ it 'returns bad request' do
+ post api(endpoint, user), params: {}
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ %w[merge_requests commits].each do |postfix|
+ context 'with correct params' do
+ let(:known_event_postfix) { postfix }
+
+ it 'returns status ok' do
+ expect(Gitlab::UsageDataCounters::BaseCounter).to receive(:count).with(known_event_postfix)
+ post api(endpoint, user), params: { event: known_event }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'with unknown event' do
+ before do
+ skip_feature_flags_yaml_validation
+ end
+
+ it 'returns status ok' do
+ expect(Gitlab::UsageDataCounters::BaseCounter).not_to receive(:count)
+
+ post api(endpoint, user), params: { event: unknown_event }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
+
describe 'POST /usage_data/increment_unique_users' do
let(:endpoint) { '/usage_data/increment_unique_users' }
let(:known_event) { 'g_compliance_dashboard' }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 98840d6238a..2cd1483f486 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe API::Users, :do_not_mock_admin_mode do
+RSpec.describe API::Users do
let_it_be(:admin) { create(:admin) }
let_it_be(:user, reload: true) { create(:user, username: 'user.with.dot') }
let_it_be(:key) { create(:key, user: user) }
@@ -2510,6 +2510,98 @@ RSpec.describe API::Users, :do_not_mock_admin_mode do
end
end
+ context 'approve pending user' do
+ shared_examples '404' do
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+ end
+
+ describe 'POST /users/:id/approve' do
+ subject(:approve) { post api("/users/#{user_id}/approve", api_user) }
+
+ let_it_be(:pending_user) { create(:user, :blocked_pending_approval) }
+ let_it_be(:deactivated_user) { create(:user, :deactivated) }
+ let_it_be(:blocked_user) { create(:user, :blocked) }
+
+ context 'performed by a non-admin user' do
+ let(:api_user) { user }
+ let(:user_id) { pending_user.id }
+
+ it 'is not authorized to perform the action' do
+ expect { approve }.not_to change { pending_user.reload.state }
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq('You are not allowed to approve a user')
+ end
+ end
+
+ context 'performed by an admin user' do
+ let(:api_user) { admin }
+
+ context 'for a deactivated user' do
+ let(:user_id) { deactivated_user.id }
+
+ it 'does not approve a deactivated user' do
+ expect { approve }.not_to change { deactivated_user.reload.state }
+ expect(response).to have_gitlab_http_status(:conflict)
+ expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval')
+ end
+ end
+
+ context 'for an pending approval user' do
+ let(:user_id) { pending_user.id }
+
+ it 'returns 201' do
+ expect { approve }.to change { pending_user.reload.state }.to('active')
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['message']).to eq('Success')
+ end
+ end
+
+ context 'for an active user' do
+ let(:user_id) { user.id }
+
+ it 'returns 201' do
+ expect { approve }.not_to change { user.reload.state }
+ expect(response).to have_gitlab_http_status(:conflict)
+ expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval')
+ end
+ end
+
+ context 'for a blocked user' do
+ let(:user_id) { blocked_user.id }
+
+ it 'returns 403' do
+ expect { approve }.not_to change { blocked_user.reload.state }
+ expect(response).to have_gitlab_http_status(:conflict)
+ expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval')
+ end
+ end
+
+ context 'for a ldap blocked user' do
+ let(:user_id) { ldap_blocked_user.id }
+
+ it 'returns 403' do
+ expect { approve }.not_to change { ldap_blocked_user.reload.state }
+ expect(response).to have_gitlab_http_status(:conflict)
+ expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval')
+ end
+ end
+
+ context 'for a user that does not exist' do
+ let(:user_id) { non_existing_record_id }
+
+ before do
+ approve
+ end
+
+ it_behaves_like '404'
+ end
+ end
+ end
+ end
+
describe 'POST /users/:id/block' do
let(:blocked_user) { create(:user, state: 'blocked') }
diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb
index 86ddf4a78d8..e7d9ba99743 100644
--- a/spec/requests/api/v3/github_spec.rb
+++ b/spec/requests/api/v3/github_spec.rb
@@ -383,7 +383,7 @@ RSpec.describe API::V3::Github do
it 'counts Jira Cloud integration as enabled' do
user_agent = 'Jira DVCS Connector Vertigo/4.42.0'
- Timecop.freeze do
+ freeze_time do
jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", user), user_agent
expect(project.reload.jira_dvcs_cloud_last_sync_at).to be_like_time(Time.now)
@@ -393,7 +393,7 @@ RSpec.describe API::V3::Github do
it 'counts Jira Server integration as enabled' do
user_agent = 'Jira DVCS Connector/3.2.4'
- Timecop.freeze do
+ freeze_time do
jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", user), user_agent
expect(project.reload.jira_dvcs_server_last_sync_at).to be_like_time(Time.now)
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 32aeeed43b6..bc89dc2fa77 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -809,12 +809,24 @@ RSpec.describe 'Git HTTP requests' do
context 'administrator' do
let(:user) { create(:admin) }
- it_behaves_like 'can download code only'
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it_behaves_like 'can download code only'
- it 'downloads from other project get status 403' do
- clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token
+ it 'downloads from other project get status 403' do
+ clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it_behaves_like 'can download code only'
+
+ it 'downloads from other project get status 404' do
+ clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
diff --git a/spec/controllers/ide_controller_spec.rb b/spec/requests/ide_controller_spec.rb
index 39d92846863..805c1f1d82b 100644
--- a/spec/controllers/ide_controller_spec.rb
+++ b/spec/requests/ide_controller_spec.rb
@@ -12,6 +12,6 @@ RSpec.describe IdeController do
it 'increases the views counter' do
expect(Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_views_count)
- get :index
+ get ide_url
end
end
diff --git a/spec/requests/import/gitlab_groups_controller_spec.rb b/spec/requests/import/gitlab_groups_controller_spec.rb
index 4125c5c7c7a..51f1363cf1c 100644
--- a/spec/requests/import/gitlab_groups_controller_spec.rb
+++ b/spec/requests/import/gitlab_groups_controller_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Import::GitlabGroupsController do
include WorkhorseHelpers
+ let_it_be(:user) { create(:user) }
let(:import_path) { "#{Dir.tmpdir}/gitlab_groups_controller_spec" }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_headers) do
@@ -28,8 +29,6 @@ RSpec.describe Import::GitlabGroupsController do
describe 'POST create' do
subject(:import_request) { upload_archive(file_upload, workhorse_headers, request_params) }
- let_it_be(:user) { create(:user) }
-
let(:file) { File.join('spec', %w[fixtures group_export.tar.gz]) }
let(:file_upload) { fixture_file_upload(file) }
@@ -194,67 +193,11 @@ RSpec.describe Import::GitlabGroupsController do
end
describe 'POST authorize' do
- let_it_be(:user) { create(:user) }
-
- before do
- login_as(user)
- end
-
- context 'when using a workhorse header' do
- subject(:authorize_request) { post authorize_import_gitlab_group_path, headers: workhorse_headers }
-
- it 'authorizes the request' do
- authorize_request
-
- expect(response).to have_gitlab_http_status :ok
- expect(response.media_type).to eq Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
- expect(json_response['TempPath']).to eq ImportExportUploader.workhorse_local_upload_path
- end
- end
-
- context 'when the request bypasses gitlab-workhorse' do
- subject(:authorize_request) { post authorize_import_gitlab_group_path }
-
- it 'rejects the request' do
- expect { authorize_request }.to raise_error(JWT::DecodeError)
- end
- end
-
- context 'when direct upload is enabled' do
- subject(:authorize_request) { post authorize_import_gitlab_group_path, headers: workhorse_headers }
+ it_behaves_like 'handle uploads authorize request' do
+ let(:uploader_class) { ImportExportUploader }
+ let(:maximum_size) { Gitlab::CurrentSettings.max_import_size.megabytes }
- before do
- stub_uploads_object_storage(ImportExportUploader, enabled: true, direct_upload: true)
- end
-
- it 'accepts the request and stores the files' do
- authorize_request
-
- expect(response).to have_gitlab_http_status :ok
- expect(response.media_type).to eq Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
- expect(json_response).not_to have_key 'TempPath'
-
- expect(json_response['RemoteObject'].keys)
- .to include('ID', 'GetURL', 'StoreURL', 'DeleteURL', 'MultipartUpload')
- end
- end
-
- context 'when direct upload is disabled' do
- subject(:authorize_request) { post authorize_import_gitlab_group_path, headers: workhorse_headers }
-
- before do
- stub_uploads_object_storage(ImportExportUploader, enabled: true, direct_upload: false)
- end
-
- it 'handles the local file' do
- authorize_request
-
- expect(response).to have_gitlab_http_status :ok
- expect(response.media_type).to eq Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
-
- expect(json_response['TempPath']).to eq ImportExportUploader.workhorse_local_upload_path
- expect(json_response['RemoteObject']).to be_nil
- end
+ subject { post authorize_import_gitlab_group_path, headers: workhorse_headers }
end
end
end
diff --git a/spec/requests/import/gitlab_projects_controller_spec.rb b/spec/requests/import/gitlab_projects_controller_spec.rb
index c1ac5a9f2c8..d7d4de21a33 100644
--- a/spec/requests/import/gitlab_projects_controller_spec.rb
+++ b/spec/requests/import/gitlab_projects_controller_spec.rb
@@ -84,56 +84,11 @@ RSpec.describe Import::GitlabProjectsController do
end
describe 'POST authorize' do
- subject { post authorize_import_gitlab_project_path, headers: workhorse_headers }
+ it_behaves_like 'handle uploads authorize request' do
+ let(:uploader_class) { ImportExportUploader }
+ let(:maximum_size) { Gitlab::CurrentSettings.max_import_size.megabytes }
- it 'authorizes importing project with workhorse header' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response['TempPath']).to eq(ImportExportUploader.workhorse_local_upload_path)
- end
-
- it 'rejects requests that bypassed gitlab-workhorse' do
- workhorse_headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
-
- expect { subject }.to raise_error(JWT::DecodeError)
- end
-
- context 'when using remote storage' do
- context 'when direct upload is enabled' do
- before do
- stub_uploads_object_storage(ImportExportUploader, enabled: true, direct_upload: true)
- end
-
- it 'responds with status 200, location of file remote store and object details' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response).not_to have_key('TempPath')
- expect(json_response['RemoteObject']).to have_key('ID')
- expect(json_response['RemoteObject']).to have_key('GetURL')
- expect(json_response['RemoteObject']).to have_key('StoreURL')
- expect(json_response['RemoteObject']).to have_key('DeleteURL')
- expect(json_response['RemoteObject']).to have_key('MultipartUpload')
- end
- end
-
- context 'when direct upload is disabled' do
- before do
- stub_uploads_object_storage(ImportExportUploader, enabled: true, direct_upload: false)
- end
-
- it 'handles as a local file' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response['TempPath']).to eq(ImportExportUploader.workhorse_local_upload_path)
- expect(json_response['RemoteObject']).to be_nil
- end
- end
+ subject { post authorize_import_gitlab_project_path, headers: workhorse_headers }
end
end
end
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index fe6c0f0a556..e154e691d5f 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -5,13 +5,13 @@ require 'spec_helper'
RSpec.describe JwtController do
include_context 'parsed logs'
- let(:service) { double(execute: {}) }
- let(:service_class) { double(new: service) }
- let(:service_name) { 'test' }
+ let(:service) { double(execute: {} ) }
+ let(:service_class) { Auth::ContainerRegistryAuthenticationService }
+ let(:service_name) { 'container_registry' }
let(:parameters) { { service: service_name } }
before do
- stub_const('JwtController::SERVICES', service_name => service_class)
+ allow(service_class).to receive(:new).and_return(service)
end
shared_examples 'user logging' do
@@ -22,194 +22,266 @@ RSpec.describe JwtController do
end
end
- context 'existing service' do
- subject! { get '/jwt/auth', params: parameters }
+ context 'authenticating against container registry' do
+ context 'existing service' do
+ subject! { get '/jwt/auth', params: parameters }
- it { expect(response).to have_gitlab_http_status(:ok) }
+ it { expect(response).to have_gitlab_http_status(:ok) }
- context 'returning custom http code' do
- let(:service) { double(execute: { http_status: 505 }) }
+ context 'returning custom http code' do
+ let(:service) { double(execute: { http_status: 505 }) }
- it { expect(response).to have_gitlab_http_status(:http_version_not_supported) }
+ it { expect(response).to have_gitlab_http_status(:http_version_not_supported) }
+ end
end
- end
- context 'when using authenticated request' do
- shared_examples 'rejecting a blocked user' do
- context 'with blocked user' do
- let(:user) { create(:user, :blocked) }
+ context 'when using authenticated request' do
+ shared_examples 'rejecting a blocked user' do
+ context 'with blocked user' do
+ let(:user) { create(:user, :blocked) }
- it 'rejects the request as unauthorized' do
- expect(response).to have_gitlab_http_status(:unauthorized)
- expect(response.body).to include('HTTP Basic: Access denied')
+ it 'rejects the request as unauthorized' do
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(response.body).to include('HTTP Basic: Access denied')
+ end
end
end
- end
- context 'using CI token' do
- let(:user) { create(:user) }
- let(:build) { create(:ci_build, :running, user: user) }
- let(:project) { build.project }
- let(:headers) { { authorization: credentials('gitlab-ci-token', build.token) } }
+ context 'using CI token' do
+ let(:user) { create(:user) }
+ let(:build) { create(:ci_build, :running, user: user) }
+ let(:project) { build.project }
+ let(:headers) { { authorization: credentials('gitlab-ci-token', build.token) } }
- context 'project with enabled CI' do
- subject! { get '/jwt/auth', params: parameters, headers: headers }
-
- it { expect(service_class).to have_received(:new).with(project, user, ActionController::Parameters.new(parameters).permit!) }
+ context 'project with enabled CI' do
+ subject! { get '/jwt/auth', params: parameters, headers: headers }
- it_behaves_like 'user logging'
- end
+ it { expect(service_class).to have_received(:new).with(project, user, ActionController::Parameters.new(parameters).permit!) }
- context 'project with disabled CI' do
- before do
- project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
+ it_behaves_like 'user logging'
end
- subject! { get '/jwt/auth', params: parameters, headers: headers }
+ context 'project with disabled CI' do
+ before do
+ project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
+ end
- it { expect(response).to have_gitlab_http_status(:unauthorized) }
- end
+ subject! { get '/jwt/auth', params: parameters, headers: headers }
- context 'using deploy tokens' do
- let(:deploy_token) { create(:deploy_token, read_registry: true, projects: [project]) }
- let(:headers) { { authorization: credentials(deploy_token.username, deploy_token.token) } }
+ it { expect(response).to have_gitlab_http_status(:unauthorized) }
+ end
- subject! { get '/jwt/auth', params: parameters, headers: headers }
+ context 'using deploy tokens' do
+ let(:deploy_token) { create(:deploy_token, read_registry: true, projects: [project]) }
+ let(:headers) { { authorization: credentials(deploy_token.username, deploy_token.token) } }
- it 'authenticates correctly' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(service_class).to have_received(:new).with(nil, deploy_token, ActionController::Parameters.new(parameters).permit!)
- end
+ subject! { get '/jwt/auth', params: parameters, headers: headers }
- it 'does not log a user' do
- expect(log_data.keys).not_to include(%w(username user_id))
+ it 'authenticates correctly' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(service_class).to have_received(:new).with(nil, deploy_token, ActionController::Parameters.new(parameters).permit!)
+ end
+
+ it 'does not log a user' do
+ expect(log_data.keys).not_to include(%w(username user_id))
+ end
end
- end
- context 'using personal access tokens' do
- let(:pat) { create(:personal_access_token, user: user, scopes: ['read_registry']) }
- let(:headers) { { authorization: credentials('personal_access_token', pat.token) } }
+ context 'using personal access tokens' do
+ let(:pat) { create(:personal_access_token, user: user, scopes: ['read_registry']) }
+ let(:headers) { { authorization: credentials('personal_access_token', pat.token) } }
- before do
- stub_container_registry_config(enabled: true)
+ before do
+ stub_container_registry_config(enabled: true)
+ end
+
+ subject! { get '/jwt/auth', params: parameters, headers: headers }
+
+ it 'authenticates correctly' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!)
+ end
+
+ it_behaves_like 'rejecting a blocked user'
+ it_behaves_like 'user logging'
end
+ end
+
+ context 'using User login' do
+ let(:user) { create(:user) }
+ let(:headers) { { authorization: credentials(user.username, user.password) } }
subject! { get '/jwt/auth', params: parameters, headers: headers }
- it 'authenticates correctly' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!)
- end
+ it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) }
it_behaves_like 'rejecting a blocked user'
- it_behaves_like 'user logging'
- end
- end
-
- context 'using User login' do
- let(:user) { create(:user) }
- let(:headers) { { authorization: credentials(user.username, user.password) } }
- subject! { get '/jwt/auth', params: parameters, headers: headers }
+ context 'when passing a flat array of scopes' do
+ # We use this trick to make rails to generate a query_string:
+ # scope=scope1&scope=scope2
+ # It works because :scope and 'scope' are the same as string, but different objects
+ let(:parameters) do
+ {
+ :service => service_name,
+ :scope => 'scope1',
+ 'scope' => 'scope2'
+ }
+ end
- it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) }
+ let(:service_parameters) do
+ ActionController::Parameters.new({ service: service_name, scopes: %w(scope1 scope2) }).permit!
+ end
- it_behaves_like 'rejecting a blocked user'
+ it { expect(service_class).to have_received(:new).with(nil, user, service_parameters) }
- context 'when passing a flat array of scopes' do
- # We use this trick to make rails to generate a query_string:
- # scope=scope1&scope=scope2
- # It works because :scope and 'scope' are the same as string, but different objects
- let(:parameters) do
- {
- :service => service_name,
- :scope => 'scope1',
- 'scope' => 'scope2'
- }
+ it_behaves_like 'user logging'
end
- let(:service_parameters) do
- ActionController::Parameters.new({ service: service_name, scopes: %w(scope1 scope2) }).permit!
+ context 'when user has 2FA enabled' do
+ let(:user) { create(:user, :two_factor) }
+
+ context 'without personal token' do
+ it 'rejects the authorization attempt' do
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP')
+ end
+ end
+
+ context 'with personal token' do
+ let(:access_token) { create(:personal_access_token, user: user) }
+ let(:headers) { { authorization: credentials(user.username, access_token.token) } }
+
+ it 'accepts the authorization attempt' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
end
- it { expect(service_class).to have_received(:new).with(nil, user, service_parameters) }
+ it 'does not cause session based checks to be activated' do
+ expect(Gitlab::Session).not_to receive(:with_session)
+
+ get '/jwt/auth', params: parameters, headers: headers
- it_behaves_like 'user logging'
+ expect(response).to have_gitlab_http_status(:ok)
+ end
end
- context 'when user has 2FA enabled' do
- let(:user) { create(:user, :two_factor) }
+ context 'using invalid login' do
+ let(:headers) { { authorization: credentials('invalid', 'password') } }
- context 'without personal token' do
+ context 'when internal auth is enabled' do
it 'rejects the authorization attempt' do
+ get '/jwt/auth', params: parameters, headers: headers
+
expect(response).to have_gitlab_http_status(:unauthorized)
- expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP')
+ expect(response.body).not_to include('You must use a personal access token with \'api\' scope for Git over HTTP')
end
end
- context 'with personal token' do
- let(:access_token) { create(:personal_access_token, user: user) }
- let(:headers) { { authorization: credentials(user.username, access_token.token) } }
+ context 'when internal auth is disabled' do
+ it 'rejects the authorization attempt with personal access token message' do
+ allow_next_instance_of(ApplicationSetting) do |instance|
+ allow(instance).to receive(:password_authentication_enabled_for_git?) { false }
+ end
+ get '/jwt/auth', params: parameters, headers: headers
- it 'accepts the authorization attempt' do
- expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP')
end
end
end
+ end
- it 'does not cause session based checks to be activated' do
- expect(Gitlab::Session).not_to receive(:with_session)
-
- get '/jwt/auth', params: parameters, headers: headers
+ context 'when using unauthenticated request' do
+ it 'accepts the authorization attempt' do
+ get '/jwt/auth', params: parameters
expect(response).to have_gitlab_http_status(:ok)
end
+
+ it 'allows read access' do
+ expect(service).to receive(:execute).with(authentication_abilities: Gitlab::Auth.read_only_authentication_abilities)
+
+ get '/jwt/auth', params: parameters
+ end
end
- context 'using invalid login' do
- let(:headers) { { authorization: credentials('invalid', 'password') } }
+ context 'unknown service' do
+ subject! { get '/jwt/auth', params: { service: 'unknown' } }
- context 'when internal auth is enabled' do
- it 'rejects the authorization attempt' do
- get '/jwt/auth', params: parameters, headers: headers
+ it { expect(response).to have_gitlab_http_status(:not_found) }
+ end
- expect(response).to have_gitlab_http_status(:unauthorized)
- expect(response.body).not_to include('You must use a personal access token with \'api\' scope for Git over HTTP')
- end
- end
+ def credentials(login, password)
+ ActionController::HttpAuthentication::Basic.encode_credentials(login, password)
+ end
+ end
- context 'when internal auth is disabled' do
- it 'rejects the authorization attempt with personal access token message' do
- allow_next_instance_of(ApplicationSetting) do |instance|
- allow(instance).to receive(:password_authentication_enabled_for_git?) { false }
- end
- get '/jwt/auth', params: parameters, headers: headers
+ context 'authenticating against dependency proxy' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :private, group: group) }
+ let_it_be(:group_deploy_token) { create(:deploy_token, :group, groups: [group]) }
+ let_it_be(:project_deploy_token) { create(:deploy_token, :project, projects: [project]) }
+ let_it_be(:service_name) { 'dependency_proxy' }
+ let(:headers) { { authorization: credentials(credential_user, credential_password) } }
+ let(:params) { { account: credential_user, client_id: 'docker', offline_token: true, service: service_name } }
+
+ before do
+ stub_config(dependency_proxy: { enabled: true })
+ end
- expect(response).to have_gitlab_http_status(:unauthorized)
- expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP')
- end
+ subject { get '/jwt/auth', params: params, headers: headers }
+
+ shared_examples 'with valid credentials' do
+ it 'returns token successfully' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response['token']).to be_present
end
end
- end
- context 'when using unauthenticated request' do
- it 'accepts the authorization attempt' do
- get '/jwt/auth', params: parameters
+ context 'with personal access token' do
+ let(:credential_user) { nil }
+ let(:credential_password) { personal_access_token.token }
- expect(response).to have_gitlab_http_status(:ok)
+ it_behaves_like 'with valid credentials'
end
- it 'allows read access' do
- expect(service).to receive(:execute).with(authentication_abilities: Gitlab::Auth.read_only_authentication_abilities)
+ context 'with user credentials token' do
+ let(:credential_user) { user.username }
+ let(:credential_password) { user.password }
- get '/jwt/auth', params: parameters
+ it_behaves_like 'with valid credentials'
end
- end
- context 'unknown service' do
- subject! { get '/jwt/auth', params: { service: 'unknown' } }
+ context 'with group deploy token' do
+ let(:credential_user) { group_deploy_token.username }
+ let(:credential_password) { group_deploy_token.token }
- it { expect(response).to have_gitlab_http_status(:not_found) }
+ it_behaves_like 'with valid credentials'
+ end
+
+ context 'with project deploy token' do
+ let(:credential_user) { project_deploy_token.username }
+ let(:credential_password) { project_deploy_token.token }
+
+ it_behaves_like 'with valid credentials'
+ end
+
+ context 'with invalid credentials' do
+ let(:credential_user) { 'foo' }
+ let(:credential_password) { 'bar' }
+
+ it 'returns unauthorized' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
end
def credentials(login, password)
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 48d125a37c3..535d511a459 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -6,1179 +6,1145 @@ RSpec.describe 'Git LFS API and storage' do
include ProjectForksHelper
include WorkhorseHelpers
- let_it_be(:project, reload: true) { create(:project, :repository) }
- let_it_be(:other_project) { create(:project, :repository) }
+ let_it_be(:project, reload: true) { create(:project, :empty_repo) }
let_it_be(:user) { create(:user) }
- let(:lfs_object) { create(:lfs_object, :with_file) }
- let(:headers) do
- {
- 'Authorization' => authorization,
- 'X-Sendfile-Type' => 'X-Sendfile'
- }.compact
- end
-
- let(:include_workhorse_jwt_header) { true }
- let(:authorization) { }
- let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ context 'with projects' do
+ it_behaves_like 'LFS http requests' do
+ let_it_be(:other_project, reload: true) { create(:project, :empty_repo) }
- let(:sample_oid) { lfs_object.oid }
- let(:sample_size) { lfs_object.size }
- let(:sample_object) { { 'oid' => sample_oid, 'size' => sample_size } }
- let(:non_existing_object_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
- let(:non_existing_object_size) { 1575078 }
- let(:non_existing_object) { { 'oid' => non_existing_object_oid, 'size' => non_existing_object_size } }
- let(:multiple_objects) { [sample_object, non_existing_object] }
+ let(:container) { project }
+ let(:authorize_guest) { project.add_guest(user) }
+ let(:authorize_download) { project.add_reporter(user) }
+ let(:authorize_upload) { project.add_developer(user) }
- let(:lfs_enabled) { true }
+ context 'project specific LFS settings' do
+ let(:body) { upload_body(sample_object) }
- before do
- stub_lfs_setting(enabled: lfs_enabled)
- end
+ before do
+ authorize_upload
+ project.update_attribute(:lfs_enabled, project_lfs_enabled)
- context 'project specific LFS settings' do
- let(:body) { upload_body(sample_object) }
- let(:authorization) { authorize_user }
+ subject
+ end
- before do
- project.add_maintainer(user)
- project.update_attribute(:lfs_enabled, project_lfs_enabled)
+ describe 'LFS disabled in project' do
+ let(:project_lfs_enabled) { false }
- subject
- end
+ context 'when uploading' do
+ subject(:request) { post_lfs_json(batch_url(project), body, headers) }
- describe 'LFS disabled in project' do
- let(:project_lfs_enabled) { false }
+ it_behaves_like 'LFS http 404 response'
+ end
- context 'when uploading' do
- subject { post_lfs_json(batch_url(project), body, headers) }
+ context 'when downloading' do
+ subject(:request) { get(objects_url(project, sample_oid), params: {}, headers: headers) }
- it_behaves_like 'LFS http 404 response'
- end
+ it_behaves_like 'LFS http 404 response'
+ end
+ end
- context 'when downloading' do
- subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
+ describe 'LFS enabled in project' do
+ let(:project_lfs_enabled) { true }
- it_behaves_like 'LFS http 404 response'
- end
- end
+ context 'when uploading' do
+ subject(:request) { post_lfs_json(batch_url(project), body, headers) }
- describe 'LFS enabled in project' do
- let(:project_lfs_enabled) { true }
+ it_behaves_like 'LFS http 200 response'
+ end
- context 'when uploading' do
- subject { post_lfs_json(batch_url(project), body, headers) }
+ context 'when downloading' do
+ subject(:request) { get(objects_url(project, sample_oid), params: {}, headers: headers) }
- it_behaves_like 'LFS http 200 response'
+ it_behaves_like 'LFS http 200 blob response'
+ end
+ end
end
- context 'when downloading' do
- subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
+ describe 'when fetching LFS object' do
+ subject(:request) { get objects_url(project, sample_oid), params: {}, headers: headers }
- it_behaves_like 'LFS http 200 blob response'
- end
- end
- end
+ let(:response) { request && super() }
- describe 'when fetching LFS object' do
- let(:update_permissions) { }
- let(:before_get) { }
+ before do
+ project.lfs_objects << lfs_object
+ end
- before do
- project.lfs_objects << lfs_object
- update_permissions
- before_get
+ context 'when LFS uses object storage' do
+ before do
+ authorize_download
+ end
- get objects_url(project, sample_oid), params: {}, headers: headers
- end
+ context 'when proxy download is enabled' do
+ before do
+ stub_lfs_object_storage(proxy_download: true)
+ lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+ end
- context 'when LFS uses object storage' do
- let(:authorization) { authorize_user }
+ it 'responds with the workhorse send-url' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:")
+ end
+ end
- let(:update_permissions) do
- project.add_maintainer(user)
- end
+ context 'when proxy download is disabled' do
+ before do
+ stub_lfs_object_storage(proxy_download: false)
+ lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+ end
- context 'when proxy download is enabled' do
- let(:before_get) do
- stub_lfs_object_storage(proxy_download: true)
- lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
- end
+ it 'responds with redirect' do
+ expect(response).to have_gitlab_http_status(:found)
+ end
- it 'responds with the workhorse send-url' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:")
+ it 'responds with the file location' do
+ expect(response.location).to include(lfs_object.reload.file.path)
+ end
+ end
end
- end
- context 'when proxy download is disabled' do
- let(:before_get) do
- stub_lfs_object_storage(proxy_download: false)
- lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
- end
+ context 'when deploy key is authorized' do
+ let_it_be(:key) { create(:deploy_key) }
+ let(:authorization) { authorize_deploy_key }
- it 'responds with redirect' do
- expect(response).to have_gitlab_http_status(:found)
- end
+ before do
+ project.deploy_keys << key
+ end
- it 'responds with the file location' do
- expect(response.location).to include(lfs_object.reload.file.path)
+ it_behaves_like 'LFS http 200 blob response'
end
- end
- end
- context 'when deploy key is authorized' do
- let(:key) { create(:deploy_key) }
- let(:authorization) { authorize_deploy_key }
+ context 'when using a user key (LFSToken)' do
+ let(:authorization) { authorize_user_key }
- let(:update_permissions) do
- project.deploy_keys << key
- end
+ context 'when user allowed' do
+ before do
+ authorize_download
+ end
- it_behaves_like 'LFS http 200 blob response'
- end
+ it_behaves_like 'LFS http 200 blob response'
- context 'when using a user key (LFSToken)' do
- let(:authorization) { authorize_user_key }
+ context 'when user password is expired' do
+ let_it_be(:user) { create(:user, password_expires_at: 1.minute.ago)}
- context 'when user allowed' do
- let(:update_permissions) do
- project.add_maintainer(user)
- end
+ it_behaves_like 'LFS http 401 response'
+ end
- it_behaves_like 'LFS http 200 blob response'
+ context 'when user is blocked' do
+ let_it_be(:user) { create(:user, :blocked)}
- context 'when user password is expired' do
- let(:user) { create(:user, password_expires_at: 1.minute.ago)}
+ it_behaves_like 'LFS http 401 response'
+ end
+ end
- it_behaves_like 'LFS http 401 response'
+ context 'when user not allowed' do
+ it_behaves_like 'LFS http 404 response'
+ end
end
- context 'when user is blocked' do
- let(:user) { create(:user, :blocked)}
+ context 'when build is authorized as' do
+ let(:authorization) { authorize_ci_project }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
- it_behaves_like 'LFS http 401 response'
- end
- end
+ shared_examples 'can download LFS only from own projects' do
+ context 'for owned project' do
+ let_it_be(:project) { create(:project, namespace: user.namespace) }
- context 'when user not allowed' do
- it_behaves_like 'LFS http 404 response'
- end
- end
+ it_behaves_like 'LFS http 200 blob response'
+ end
- context 'when build is authorized as' do
- let(:authorization) { authorize_ci_project }
+ context 'for member of project' do
+ before do
+ authorize_download
+ end
- shared_examples 'can download LFS only from own projects' do
- context 'for owned project' do
- let(:project) { create(:project, namespace: user.namespace) }
+ it_behaves_like 'LFS http 200 blob response'
+ end
- it_behaves_like 'LFS http 200 blob response'
- end
+ context 'for other project' do
+ let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
- context 'for member of project' do
- let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ it 'rejects downloading code' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'administrator', :enable_admin_mode do
+ let_it_be(:user) { create(:admin) }
- let(:update_permissions) do
- project.add_reporter(user)
+ it_behaves_like 'can download LFS only from own projects'
end
- it_behaves_like 'LFS http 200 blob response'
- end
+ context 'regular user' do
+ it_behaves_like 'can download LFS only from own projects'
+ end
- context 'for other project' do
- let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+ context 'does not have user' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline) }
- it 'rejects downloading code' do
- expect(response).to have_gitlab_http_status(:not_found)
+ it_behaves_like 'can download LFS only from own projects'
end
end
end
- context 'administrator' do
- let(:user) { create(:admin) }
- let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
-
- it_behaves_like 'can download LFS only from own projects'
- end
+ describe 'when handling LFS batch request' do
+ subject(:request) { post_lfs_json batch_url(project), body, headers }
- context 'regular user' do
- let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+ let(:response) { request && super() }
+ let(:lfs_chunked_encoding) { true }
- it_behaves_like 'can download LFS only from own projects'
- end
+ before do
+ stub_feature_flags(lfs_chunked_encoding: lfs_chunked_encoding)
+ project.lfs_objects << lfs_object
+ end
- context 'does not have user' do
- let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+ shared_examples 'process authorization header' do |renew_authorization:|
+ let(:response_authorization) do
+ authorization_in_action(lfs_actions.first)
+ end
- it_behaves_like 'can download LFS only from own projects'
- end
- end
- end
+ if renew_authorization
+ context 'when the authorization comes from a user' do
+ it 'returns a new valid LFS token authorization' do
+ expect(response_authorization).not_to eq(authorization)
+ end
- describe 'when handling LFS batch request' do
- let(:update_lfs_permissions) { }
- let(:update_user_permissions) { }
+ it 'returns a valid token' do
+ username, token = ::Base64.decode64(response_authorization.split(' ', 2).last).split(':', 2)
- before do
- update_lfs_permissions
- update_user_permissions
- post_lfs_json batch_url(project), body, headers
- end
+ expect(username).to eq(user.username)
+ expect(Gitlab::LfsToken.new(user).token_valid?(token)).to be_truthy
+ end
- shared_examples 'process authorization header' do |renew_authorization:|
- let(:response_authorization) do
- authorization_in_action(lfs_actions.first)
- end
+ it 'generates only one new token per each request' do
+ authorizations = lfs_actions.map do |action|
+ authorization_in_action(action)
+ end.compact
- if renew_authorization
- context 'when the authorization comes from a user' do
- it 'returns a new valid LFS token authorization' do
- expect(response_authorization).not_to eq(authorization)
+ expect(authorizations.uniq.count).to eq 1
+ end
+ end
+ else
+ context 'when the authorization comes from a token' do
+ it 'returns the same authorization header' do
+ expect(response_authorization).to eq(authorization)
+ end
+ end
end
- it 'returns a a valid token' do
- username, token = ::Base64.decode64(response_authorization.split(' ', 2).last).split(':', 2)
-
- expect(username).to eq(user.username)
- expect(Gitlab::LfsToken.new(user).token_valid?(token)).to be_truthy
+ def lfs_actions
+ json_response['objects'].map { |a| a['actions'] }.compact
end
- it 'generates only one new token per each request' do
- authorizations = lfs_actions.map do |action|
- authorization_in_action(action)
- end.compact
-
- expect(authorizations.uniq.count).to eq 1
+ def authorization_in_action(action)
+ (action['upload'] || action['download']).dig('header', 'Authorization')
end
end
- else
- context 'when the authorization comes from a token' do
- it 'returns the same authorization header' do
- expect(response_authorization).to eq(authorization)
- end
- end
- end
-
- def lfs_actions
- json_response['objects'].map { |a| a['actions'] }.compact
- end
- def authorization_in_action(action)
- (action['upload'] || action['download']).dig('header', 'Authorization')
- end
- end
+ describe 'download' do
+ let(:body) { download_body(sample_object) }
- describe 'download' do
- let(:body) { download_body(sample_object) }
+ shared_examples 'an authorized request' do |renew_authorization:|
+ context 'when downloading an LFS object that is assigned to our project' do
+ it_behaves_like 'LFS http 200 response'
- shared_examples 'an authorized request' do |renew_authorization:|
- context 'when downloading an LFS object that is assigned to our project' do
- let(:update_lfs_permissions) do
- project.lfs_objects << lfs_object
- end
+ it 'with href to download' do
+ expect(json_response['objects'].first).to include(sample_object)
+ expect(json_response['objects'].first['actions']['download']['href']).to eq(objects_url(project, sample_oid))
+ end
- it_behaves_like 'LFS http 200 response'
+ it_behaves_like 'process authorization header', renew_authorization: renew_authorization
+ end
- it 'with href to download' do
- expect(json_response['objects'].first).to include(sample_object)
- expect(json_response['objects'].first['actions']['download']['href']).to eq(objects_url(project, sample_oid))
- end
+ context 'when downloading an LFS object that is assigned to other project' do
+ before do
+ lfs_object.update!(projects: [other_project])
+ end
- it_behaves_like 'process authorization header', renew_authorization: renew_authorization
- end
+ it_behaves_like 'LFS http 200 response'
- context 'when downloading an LFS object that is assigned to other project' do
- let(:update_lfs_permissions) do
- other_project.lfs_objects << lfs_object
- end
+ it 'with an 404 for specific object' do
+ expect(json_response['objects'].first).to include(sample_object)
+ expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it")
+ end
+ end
- it_behaves_like 'LFS http 200 response'
+ context 'when downloading a LFS object that does not exist' do
+ let(:body) { download_body(non_existing_object) }
- it 'with an 404 for specific object' do
- expect(json_response['objects'].first).to include(sample_object)
- expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it")
- end
- end
+ it_behaves_like 'LFS http 200 response'
- context 'when downloading a LFS object that does not exist' do
- let(:body) { download_body(non_existing_object) }
+ it 'with an 404 for specific object' do
+ expect(json_response['objects'].first).to include(non_existing_object)
+ expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it")
+ end
+ end
- it_behaves_like 'LFS http 200 response'
+ context 'when downloading one existing and one missing LFS object' do
+ let(:body) { download_body(multiple_objects) }
- it 'with an 404 for specific object' do
- expect(json_response['objects'].first).to include(non_existing_object)
- expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it")
- end
- end
+ it_behaves_like 'LFS http 200 response'
- context 'when downloading one new and one existing LFS object' do
- let(:body) { download_body(multiple_objects) }
- let(:update_lfs_permissions) do
- project.lfs_objects << lfs_object
- end
+ it 'responds with download hypermedia link for the existing object' do
+ expect(json_response['objects'].first).to include(sample_object)
+ expect(json_response['objects'].first['actions']['download']).to include('href' => objects_url(project, sample_oid))
+ expect(json_response['objects'].last).to eq({
+ 'oid' => non_existing_object_oid,
+ 'size' => non_existing_object_size,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it"
+ }
+ })
+ end
- it_behaves_like 'LFS http 200 response'
+ it_behaves_like 'process authorization header', renew_authorization: renew_authorization
+ end
- it 'responds with download hypermedia link for the new object' do
- expect(json_response['objects'].first).to include(sample_object)
- expect(json_response['objects'].first['actions']['download']).to include('href' => objects_url(project, sample_oid))
- expect(json_response['objects'].last).to eq({
- 'oid' => non_existing_object_oid,
- 'size' => non_existing_object_size,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it"
- }
- })
- end
+ context 'when downloading two existing LFS objects' do
+ let(:body) { download_body(multiple_objects) }
+ let(:other_object) { create(:lfs_object, :with_file, oid: non_existing_object_oid, size: non_existing_object_size) }
- it_behaves_like 'process authorization header', renew_authorization: renew_authorization
- end
+ before do
+ project.lfs_objects << other_object
+ end
- context 'when downloading two existing LFS objects' do
- let(:body) { download_body(multiple_objects) }
- let(:other_object) { create(:lfs_object, :with_file, oid: non_existing_object_oid, size: non_existing_object_size) }
- let(:update_lfs_permissions) do
- project.lfs_objects << [lfs_object, other_object]
- end
+ it 'responds with the download hypermedia link for each object' do
+ expect(json_response['objects'].first).to include(sample_object)
+ expect(json_response['objects'].first['actions']['download']).to include('href' => objects_url(project, sample_oid))
- it 'responds with the download hypermedia link for each object' do
- expect(json_response['objects'].first).to include(sample_object)
- expect(json_response['objects'].first['actions']['download']).to include('href' => objects_url(project, sample_oid))
+ expect(json_response['objects'].last).to include(non_existing_object)
+ expect(json_response['objects'].last['actions']['download']).to include('href' => objects_url(project, non_existing_object_oid))
+ end
- expect(json_response['objects'].last).to include(non_existing_object)
- expect(json_response['objects'].last['actions']['download']).to include('href' => objects_url(project, non_existing_object_oid))
+ it_behaves_like 'process authorization header', renew_authorization: renew_authorization
+ end
end
- it_behaves_like 'process authorization header', renew_authorization: renew_authorization
- end
- end
+ context 'when user is authenticated' do
+ before do
+ project.add_role(user, role) if role
+ end
- context 'when user is authenticated' do
- let(:authorization) { authorize_user }
+ it_behaves_like 'an authorized request', renew_authorization: true do
+ let(:role) { :reporter }
+ end
- let(:update_user_permissions) do
- project.add_role(user, role)
- end
+ context 'when user is not a member of the project' do
+ let(:role) { nil }
- it_behaves_like 'an authorized request', renew_authorization: true do
- let(:role) { :reporter }
- end
+ it_behaves_like 'LFS http 404 response'
+ end
- context 'when user is not a member of the project' do
- let(:update_user_permissions) { nil }
+ context 'when user does not have download access' do
+ let(:role) { :guest }
- it_behaves_like 'LFS http 404 response'
- end
+ it_behaves_like 'LFS http 404 response'
+ end
- context 'when user does not have download access' do
- let(:role) { :guest }
+ context 'when user password is expired' do
+ let_it_be(:user) { create(:user, password_expires_at: 1.minute.ago)}
+ let(:role) { :reporter}
- it_behaves_like 'LFS http 404 response'
- end
+ # TODO: This should return a 404 response
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/292006
+ it_behaves_like 'LFS http 200 response'
+ end
- context 'when user password is expired' do
- let(:role) { :reporter}
- let(:user) { create(:user, password_expires_at: 1.minute.ago)}
+ context 'when user is blocked' do
+ let_it_be(:user) { create(:user, :blocked)}
+ let(:role) { :reporter}
- it 'with an 404 for specific object' do
- expect(json_response['objects'].first).to include(sample_object)
- expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it")
+ it_behaves_like 'LFS http 401 response'
+ end
end
- end
- context 'when user is blocked' do
- let(:role) { :reporter}
- let(:user) { create(:user, :blocked)}
+ context 'when using Deploy Tokens' do
+ let(:authorization) { authorize_deploy_token }
- it_behaves_like 'LFS http 401 response'
- end
- end
+ context 'when Deploy Token is not valid' do
+ let(:deploy_token) { create(:deploy_token, projects: [project], read_repository: false) }
- context 'when using Deploy Tokens' do
- let(:authorization) { authorize_deploy_token }
- let(:update_user_permissions) { nil }
- let(:role) { nil }
- let(:update_lfs_permissions) do
- project.lfs_objects << lfs_object
- end
+ it_behaves_like 'LFS http 401 response'
+ end
- context 'when Deploy Token is not valid' do
- let(:deploy_token) { create(:deploy_token, projects: [project], read_repository: false) }
+ context 'when Deploy Token is not related to the project' do
+ let(:deploy_token) { create(:deploy_token, projects: [other_project]) }
- it_behaves_like 'LFS http 401 response'
- end
+ it_behaves_like 'LFS http 401 response'
+ end
- context 'when Deploy Token is not related to the project' do
- let(:deploy_token) { create(:deploy_token, projects: [other_project]) }
+ # TODO: We should fix this test case that causes flakyness by alternating the result of the above test cases.
+ context 'when Deploy Token is valid' do
+ let(:deploy_token) { create(:deploy_token, projects: [project]) }
- it_behaves_like 'LFS http 401 response'
- end
+ it_behaves_like 'an authorized request', renew_authorization: false
+ end
+ end
- # TODO: We should fix this test case that causes flakyness by alternating the result of the above test cases.
- context 'when Deploy Token is valid' do
- let(:deploy_token) { create(:deploy_token, projects: [project]) }
+ context 'when build is authorized as' do
+ let(:authorization) { authorize_ci_project }
- it_behaves_like 'an authorized request', renew_authorization: false
- end
- end
+ shared_examples 'can download LFS only from own projects' do |renew_authorization:|
+ context 'for own project' do
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
- context 'when build is authorized as' do
- let(:authorization) { authorize_ci_project }
+ before do
+ authorize_download
+ end
- let(:update_lfs_permissions) do
- project.lfs_objects << lfs_object
- end
+ it_behaves_like 'an authorized request', renew_authorization: renew_authorization
+ end
- shared_examples 'can download LFS only from own projects' do |renew_authorization:|
- context 'for own project' do
- let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ context 'for other project' do
+ let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
- let(:update_user_permissions) do
- project.add_reporter(user)
+ it 'rejects downloading code' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
end
- it_behaves_like 'an authorized request', renew_authorization: renew_authorization
- end
-
- context 'for other project' do
- let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+ context 'administrator', :enable_admin_mode do
+ let_it_be(:user) { create(:admin) }
+ let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
- it 'rejects downloading code' do
- expect(response).to have_gitlab_http_status(:not_found)
+ it_behaves_like 'can download LFS only from own projects', renew_authorization: true
end
- end
- end
- context 'administrator' do
- let(:user) { create(:admin) }
- let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
-
- it_behaves_like 'can download LFS only from own projects', renew_authorization: true
- end
+ context 'regular user' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
- context 'regular user' do
- let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
-
- it_behaves_like 'can download LFS only from own projects', renew_authorization: true
- end
+ it_behaves_like 'can download LFS only from own projects', renew_authorization: true
+ end
- context 'does not have user' do
- let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+ context 'does not have user' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline) }
- it_behaves_like 'can download LFS only from own projects', renew_authorization: false
- end
- end
+ it_behaves_like 'can download LFS only from own projects', renew_authorization: false
+ end
+ end
- context 'when user is not authenticated' do
- describe 'is accessing public project' do
- let(:project) { create(:project, :public) }
+ context 'when user is not authenticated' do
+ let(:authorization) { nil }
- let(:update_lfs_permissions) do
- project.lfs_objects << lfs_object
- end
+ describe 'is accessing public project' do
+ let_it_be(:project) { create(:project, :public) }
- it_behaves_like 'LFS http 200 response'
+ it_behaves_like 'LFS http 200 response'
- it 'returns href to download' do
- expect(json_response).to eq({
- 'objects' => [
- {
- 'oid' => sample_oid,
- 'size' => sample_size,
- 'authenticated' => true,
- 'actions' => {
- 'download' => {
- 'href' => objects_url(project, sample_oid),
- 'header' => {}
+ it 'returns href to download' do
+ expect(json_response).to eq({
+ 'objects' => [
+ {
+ 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'authenticated' => true,
+ 'actions' => {
+ 'download' => {
+ 'href' => objects_url(project, sample_oid),
+ 'header' => {}
+ }
+ }
}
- }
- }
- ]
- })
- end
- end
+ ]
+ })
+ end
+ end
- describe 'is accessing non-public project' do
- let(:update_lfs_permissions) do
- project.lfs_objects << lfs_object
+ describe 'is accessing non-public project' do
+ it_behaves_like 'LFS http 401 response'
+ end
end
-
- it_behaves_like 'LFS http 401 response'
end
- end
- end
-
- describe 'upload' do
- let(:project) { create(:project, :public) }
- let(:body) { upload_body(sample_object) }
- shared_examples 'pushes new LFS objects' do |renew_authorization:|
- let(:sample_size) { 150.megabytes }
- let(:sample_oid) { non_existing_object_oid }
+ describe 'upload' do
+ let_it_be(:project) { create(:project, :public) }
+ let(:body) { upload_body(sample_object) }
- it_behaves_like 'LFS http 200 response'
+ shared_examples 'pushes new LFS objects' do |renew_authorization:|
+ let(:sample_size) { 150.megabytes }
+ let(:sample_oid) { non_existing_object_oid }
- it 'responds with upload hypermedia link' do
- expect(json_response['objects']).to be_kind_of(Array)
- expect(json_response['objects'].first).to include(sample_object)
- expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size))
- expect(json_response['objects'].first['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream')
- end
-
- it_behaves_like 'process authorization header', renew_authorization: renew_authorization
- end
+ it_behaves_like 'LFS http 200 response'
- describe 'when request is authenticated' do
- describe 'when user has project push access' do
- let(:authorization) { authorize_user }
+ it 'responds with upload hypermedia link' do
+ expect(json_response['objects']).to be_kind_of(Array)
+ expect(json_response['objects'].first).to include(sample_object)
+ expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size))
- let(:update_user_permissions) do
- project.add_developer(user)
- end
+ headers = json_response['objects'].first['actions']['upload']['header']
+ expect(headers['Content-Type']).to eq('application/octet-stream')
+ expect(headers['Transfer-Encoding']).to eq('chunked')
+ end
- context 'when pushing an LFS object that already exists' do
- shared_examples_for 'batch upload with existing LFS object' do
- it_behaves_like 'LFS http 200 response'
+ context 'when lfs_chunked_encoding feature is disabled' do
+ let(:lfs_chunked_encoding) { false }
- it 'responds with links the object to the project' do
+ it 'responds with upload hypermedia link' do
expect(json_response['objects']).to be_kind_of(Array)
expect(json_response['objects'].first).to include(sample_object)
- expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
- expect(lfs_object.projects.pluck(:id)).to include(other_project.id)
expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size))
- expect(json_response['objects'].first['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream')
- end
- it_behaves_like 'process authorization header', renew_authorization: true
+ headers = json_response['objects'].first['actions']['upload']['header']
+ expect(headers['Content-Type']).to eq('application/octet-stream')
+ expect(headers['Transfer-Encoding']).to be_nil
+ end
end
- let(:update_lfs_permissions) do
- other_project.lfs_objects << lfs_object
- end
+ it_behaves_like 'process authorization header', renew_authorization: renew_authorization
+ end
- context 'in another project' do
- it_behaves_like 'batch upload with existing LFS object'
- end
+ describe 'when request is authenticated' do
+ describe 'when user has project push access' do
+ before do
+ authorize_upload
+ end
- context 'in source of fork project' do
- let(:project) { fork_project(other_project) }
+ context 'when pushing an LFS object that already exists' do
+ shared_examples_for 'batch upload with existing LFS object' do
+ it_behaves_like 'LFS http 200 response'
- it_behaves_like 'batch upload with existing LFS object'
- end
- end
+ it 'responds with links to the object in the project' do
+ expect(json_response['objects']).to be_kind_of(Array)
+ expect(json_response['objects'].first).to include(sample_object)
+ expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
+ expect(lfs_object.projects.pluck(:id)).to include(other_project.id)
+ expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size))
- context 'when pushing a LFS object that does not exist' do
- it_behaves_like 'pushes new LFS objects', renew_authorization: true
- end
+ headers = json_response['objects'].first['actions']['upload']['header']
+ expect(headers['Content-Type']).to eq('application/octet-stream')
+ expect(headers['Transfer-Encoding']).to eq('chunked')
+ end
- context 'when pushing one new and one existing LFS object' do
- let(:body) { upload_body(multiple_objects) }
- let(:update_lfs_permissions) do
- project.lfs_objects << lfs_object
- end
+ it_behaves_like 'process authorization header', renew_authorization: true
+ end
- it_behaves_like 'LFS http 200 response'
+ context 'in another project' do
+ before do
+ lfs_object.update!(projects: [other_project])
+ end
- it 'responds with upload hypermedia link for the new object' do
- expect(json_response['objects']).to be_kind_of(Array)
+ it_behaves_like 'batch upload with existing LFS object'
+ end
- expect(json_response['objects'].first).to include(sample_object)
- expect(json_response['objects'].first).not_to have_key('actions')
+ context 'in source of fork project' do
+ let(:project) { fork_project(other_project) }
- expect(json_response['objects'].last).to include(non_existing_object)
- expect(json_response['objects'].last['actions']['upload']['href']).to eq(objects_url(project, non_existing_object_oid, non_existing_object_size))
- expect(json_response['objects'].last['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream')
- end
+ before do
+ lfs_object.update!(projects: [other_project])
+ end
- it_behaves_like 'process authorization header', renew_authorization: true
- end
- end
+ it_behaves_like 'batch upload with existing LFS object'
+ end
+ end
- context 'when user does not have push access' do
- let(:authorization) { authorize_user }
+ context 'when pushing a LFS object that does not exist' do
+ it_behaves_like 'pushes new LFS objects', renew_authorization: true
+ end
- it_behaves_like 'LFS http 403 response'
- end
+ context 'when pushing one new and one existing LFS object' do
+ let(:body) { upload_body(multiple_objects) }
- context 'when build is authorized' do
- let(:authorization) { authorize_ci_project }
+ it_behaves_like 'LFS http 200 response'
- context 'build has an user' do
- let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+ it 'responds with upload hypermedia link for the new object' do
+ expect(json_response['objects']).to be_kind_of(Array)
- context 'tries to push to own project' do
- it_behaves_like 'LFS http 403 response'
- end
+ expect(json_response['objects'].first).to include(sample_object)
+ expect(json_response['objects'].first).not_to have_key('actions')
- context 'tries to push to other project' do
- let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+ expect(json_response['objects'].last).to include(non_existing_object)
+ expect(json_response['objects'].last['actions']['upload']['href']).to eq(objects_url(project, non_existing_object_oid, non_existing_object_size))
+
+ headers = json_response['objects'].last['actions']['upload']['header']
+ expect(headers['Content-Type']).to eq('application/octet-stream')
+ expect(headers['Transfer-Encoding']).to eq('chunked')
+ end
+
+ it_behaves_like 'process authorization header', renew_authorization: true
+ end
+ end
- # I'm not sure what this tests that is different from the previous test
+ context 'when user does not have push access' do
it_behaves_like 'LFS http 403 response'
end
- end
- context 'does not have user' do
- let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+ context 'when build is authorized' do
+ let(:authorization) { authorize_ci_project }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
- it_behaves_like 'LFS http 403 response'
- end
- end
+ context 'build has an user' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
- context 'when deploy key has project push access' do
- let(:key) { create(:deploy_key) }
- let(:authorization) { authorize_deploy_key }
+ context 'tries to push to own project' do
+ it_behaves_like 'LFS http 403 response'
+ end
- let(:update_user_permissions) do
- project.deploy_keys_projects.create!(deploy_key: key, can_push: true)
- end
+ context 'tries to push to other project' do
+ let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
- it_behaves_like 'pushes new LFS objects', renew_authorization: false
- end
- end
+ # I'm not sure what this tests that is different from the previous test
+ it_behaves_like 'LFS http 403 response'
+ end
+ end
- context 'when user is not authenticated' do
- context 'when user has push access' do
- let(:update_user_permissions) do
- project.add_maintainer(user)
- end
+ context 'does not have user' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline) }
- it_behaves_like 'LFS http 401 response'
- end
+ it_behaves_like 'LFS http 403 response'
+ end
+ end
- context 'when user does not have push access' do
- it_behaves_like 'LFS http 401 response'
- end
- end
- end
+ context 'when deploy key has project push access' do
+ let(:key) { create(:deploy_key) }
+ let(:authorization) { authorize_deploy_key }
- describe 'unsupported' do
- let(:authorization) { authorize_user }
- let(:body) { request_body('other', sample_object) }
+ before do
+ project.deploy_keys_projects.create!(deploy_key: key, can_push: true)
+ end
- it_behaves_like 'LFS http 404 response'
- end
- end
+ it_behaves_like 'pushes new LFS objects', renew_authorization: false
+ end
+ end
- describe 'when handling LFS batch request on a read-only GitLab instance' do
- let(:authorization) { authorize_user }
+ context 'when user is not authenticated' do
+ let(:authorization) { nil }
- subject { post_lfs_json(batch_url(project), body, headers) }
+ context 'when user has push access' do
+ before do
+ authorize_upload
+ end
- before do
- allow(Gitlab::Database).to receive(:read_only?) { true }
+ it_behaves_like 'LFS http 401 response'
+ end
- project.add_maintainer(user)
+ context 'when user does not have push access' do
+ it_behaves_like 'LFS http 401 response'
+ end
+ end
+ end
- subject
- end
+ describe 'unsupported' do
+ let(:body) { request_body('other', sample_object) }
- context 'when downloading' do
- let(:body) { download_body(sample_object) }
+ it_behaves_like 'LFS http 404 response'
+ end
+ end
- it_behaves_like 'LFS http 200 response'
- end
+ describe 'when handling LFS batch request on a read-only GitLab instance' do
+ subject { post_lfs_json(batch_url(project), body, headers) }
- context 'when uploading' do
- let(:body) { upload_body(sample_object) }
+ before do
+ allow(Gitlab::Database).to receive(:read_only?) { true }
- it_behaves_like 'LFS http expected response code and message' do
- let(:response_code) { 403 }
- let(:message) { 'You cannot write to this read-only GitLab instance.' }
- end
- end
- end
+ project.add_maintainer(user)
- describe 'when pushing a LFS object' do
- shared_examples 'unauthorized' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- put_authorize
+ subject
end
- it_behaves_like 'LFS http 401 response'
- end
+ context 'when downloading' do
+ let(:body) { download_body(sample_object) }
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- put_finalize
+ it_behaves_like 'LFS http 200 response'
end
- it_behaves_like 'LFS http 401 response'
- end
+ context 'when uploading' do
+ let(:body) { upload_body(sample_object) }
- context 'and request is sent with a malformed headers' do
- before do
- put_finalize('/etc/passwd')
+ it_behaves_like 'LFS http expected response code and message' do
+ let(:response_code) { 403 }
+ let(:message) { 'You cannot write to this read-only GitLab instance.' }
+ end
end
-
- it_behaves_like 'LFS http 401 response'
end
- end
- shared_examples 'forbidden' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- put_authorize
- end
+ describe 'when pushing a LFS object' do
+ let(:include_workhorse_jwt_header) { true }
- it_behaves_like 'LFS http 403 response'
- end
+ shared_examples 'unauthorized' do
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- put_finalize
- end
+ it_behaves_like 'LFS http 401 response'
+ end
- it_behaves_like 'LFS http 403 response'
- end
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
- context 'and request is sent with a malformed headers' do
- before do
- put_finalize('/etc/passwd')
+ it_behaves_like 'LFS http 401 response'
+ end
+
+ context 'and request is sent with a malformed headers' do
+ before do
+ put_finalize('/etc/passwd')
+ end
+
+ it_behaves_like 'LFS http 401 response'
+ end
end
- it_behaves_like 'LFS http 403 response'
- end
- end
+ shared_examples 'forbidden' do
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
- describe 'to one project' do
- describe 'when user is authenticated' do
- let(:authorization) { authorize_user }
+ it_behaves_like 'LFS http 403 response'
+ end
- describe 'when user has push access to the project' do
- before do
- project.add_developer(user)
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
+
+ it_behaves_like 'LFS http 403 response'
end
- context 'and the request bypassed workhorse' do
- it 'raises an exception' do
- expect { put_authorize(verified: false) }.to raise_error JWT::DecodeError
+ context 'and request is sent with a malformed headers' do
+ before do
+ put_finalize('/etc/passwd')
end
+
+ it_behaves_like 'LFS http 403 response'
end
+ end
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- shared_examples 'a valid response' do
+ describe 'to one project' do
+ describe 'when user is authenticated' do
+ describe 'when user has push access to the project' do
before do
- put_authorize
+ project.add_developer(user)
end
- it_behaves_like 'LFS http 200 workhorse response'
- end
-
- shared_examples 'a local file' do
- it_behaves_like 'a valid response' do
- it 'responds with status 200, location of LFS store and object details' do
- expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
- expect(json_response['RemoteObject']).to be_nil
- expect(json_response['LfsOid']).to eq(sample_oid)
- expect(json_response['LfsSize']).to eq(sample_size)
+ context 'and the request bypassed workhorse' do
+ it 'raises an exception' do
+ expect { put_authorize(verified: false) }.to raise_error JWT::DecodeError
end
end
- end
- context 'when using local storage' do
- it_behaves_like 'a local file'
- end
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ shared_examples 'a valid response' do
+ before do
+ put_authorize
+ end
- context 'when using remote storage' do
- context 'when direct upload is enabled' do
- before do
- stub_lfs_object_storage(enabled: true, direct_upload: true)
+ it_behaves_like 'LFS http 200 workhorse response'
end
- it_behaves_like 'a valid response' do
- it 'responds with status 200, location of LFS remote store and object details' do
- expect(json_response).not_to have_key('TempPath')
- expect(json_response['RemoteObject']).to have_key('ID')
- expect(json_response['RemoteObject']).to have_key('GetURL')
- expect(json_response['RemoteObject']).to have_key('StoreURL')
- expect(json_response['RemoteObject']).to have_key('DeleteURL')
- expect(json_response['RemoteObject']).not_to have_key('MultipartUpload')
- expect(json_response['LfsOid']).to eq(sample_oid)
- expect(json_response['LfsSize']).to eq(sample_size)
+ shared_examples 'a local file' do
+ it_behaves_like 'a valid response' do
+ it 'responds with status 200, location of LFS store and object details' do
+ expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
+ expect(json_response['RemoteObject']).to be_nil
+ expect(json_response['LfsOid']).to eq(sample_oid)
+ expect(json_response['LfsSize']).to eq(sample_size)
+ end
end
end
- end
- context 'when direct upload is disabled' do
- before do
- stub_lfs_object_storage(enabled: true, direct_upload: false)
+ context 'when using local storage' do
+ it_behaves_like 'a local file'
end
- it_behaves_like 'a local file'
- end
- end
- end
+ context 'when using remote storage' do
+ context 'when direct upload is enabled' do
+ before do
+ stub_lfs_object_storage(enabled: true, direct_upload: true)
+ end
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- put_finalize
- end
+ it_behaves_like 'a valid response' do
+ it 'responds with status 200, location of LFS remote store and object details' do
+ expect(json_response).not_to have_key('TempPath')
+ expect(json_response['RemoteObject']).to have_key('ID')
+ expect(json_response['RemoteObject']).to have_key('GetURL')
+ expect(json_response['RemoteObject']).to have_key('StoreURL')
+ expect(json_response['RemoteObject']).to have_key('DeleteURL')
+ expect(json_response['RemoteObject']).not_to have_key('MultipartUpload')
+ expect(json_response['LfsOid']).to eq(sample_oid)
+ expect(json_response['LfsSize']).to eq(sample_size)
+ end
+ end
+ end
- it_behaves_like 'LFS http 200 response'
+ context 'when direct upload is disabled' do
+ before do
+ stub_lfs_object_storage(enabled: true, direct_upload: false)
+ end
- it 'LFS object is linked to the project' do
- expect(lfs_object.projects.pluck(:id)).to include(project.id)
- end
- end
+ it_behaves_like 'a local file'
+ end
+ end
+ end
- context 'and request to finalize the upload is not sent by gitlab-workhorse' do
- it 'fails with a JWT decode error' do
- expect { put_finalize(lfs_tmp_file, verified: false) }.to raise_error(JWT::DecodeError)
- end
- end
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
- context 'and workhorse requests upload finalize for a new LFS object' do
- before do
- lfs_object.destroy!
- end
+ it_behaves_like 'LFS http 200 response'
- context 'with object storage disabled' do
- it "doesn't attempt to migrate file to object storage" do
- expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
+ it 'LFS object is linked to the project' do
+ expect(lfs_object.projects.pluck(:id)).to include(project.id)
+ end
+ end
- put_finalize(with_tempfile: true)
+ context 'and request to finalize the upload is not sent by gitlab-workhorse' do
+ it 'fails with a JWT decode error' do
+ expect { put_finalize(lfs_tmp_file, verified: false) }.to raise_error(JWT::DecodeError)
+ end
end
- end
- context 'with object storage enabled' do
- context 'and direct upload enabled' do
- let!(:fog_connection) do
- stub_lfs_object_storage(direct_upload: true)
+ context 'and workhorse requests upload finalize for a new LFS object' do
+ before do
+ lfs_object.destroy!
end
- let(:tmp_object) do
- fog_connection.directories.new(key: 'lfs-objects').files.create( # rubocop: disable Rails/SaveBang
- key: 'tmp/uploads/12312300',
- body: 'content'
- )
+ context 'with object storage disabled' do
+ it "doesn't attempt to migrate file to object storage" do
+ expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
+
+ put_finalize(with_tempfile: true)
+ end
end
- ['123123', '../../123123'].each do |remote_id|
- context "with invalid remote_id: #{remote_id}" do
- subject do
- put_finalize(remote_object: tmp_object, args: {
- 'file.remote_id' => remote_id
- })
+ context 'with object storage enabled' do
+ context 'and direct upload enabled' do
+ let!(:fog_connection) do
+ stub_lfs_object_storage(direct_upload: true)
end
- it 'responds with status 403' do
- subject
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'lfs-objects').files.create( # rubocop: disable Rails/SaveBang
+ key: 'tmp/uploads/12312300',
+ body: 'content'
+ )
+ end
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ subject do
+ put_finalize(remote_object: tmp_object, args: {
+ 'file.remote_id' => remote_id
+ })
+ end
+
+ it 'responds with status 403' do
+ subject
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
end
- end
- end
- context 'with valid remote_id' do
- subject do
- put_finalize(remote_object: tmp_object, args: {
- 'file.remote_id' => '12312300',
- 'file.name' => 'name'
- })
- end
+ context 'with valid remote_id' do
+ subject do
+ put_finalize(remote_object: tmp_object, args: {
+ 'file.remote_id' => '12312300',
+ 'file.name' => 'name'
+ })
+ end
- it 'responds with status 200' do
- subject
+ it 'responds with status 200' do
+ subject
- expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to have_gitlab_http_status(:ok)
- object = LfsObject.find_by_oid(sample_oid)
- expect(object).to be_present
- expect(object.file.read).to eq(tmp_object.body)
- end
+ object = LfsObject.find_by_oid(sample_oid)
+ expect(object).to be_present
+ expect(object.file.read).to eq(tmp_object.body)
+ end
+
+ it 'schedules migration of file to object storage' do
+ subject
+
+ expect(LfsObject.last.projects).to include(project)
+ end
- it 'schedules migration of file to object storage' do
- subject
+ it 'have valid file' do
+ subject
- expect(LfsObject.last.projects).to include(project)
+ expect(LfsObject.last.file_store).to eq(ObjectStorage::Store::REMOTE)
+ expect(LfsObject.last.file).to be_exists
+ end
+ end
end
- it 'have valid file' do
- subject
+ context 'and background upload enabled' do
+ before do
+ stub_lfs_object_storage(background_upload: true)
+ end
+
+ it 'schedules migration of file to object storage' do
+ expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('LfsObjectUploader', 'LfsObject', :file, kind_of(Numeric))
- expect(LfsObject.last.file_store).to eq(ObjectStorage::Store::REMOTE)
- expect(LfsObject.last.file).to be_exists
+ put_finalize(with_tempfile: true)
+ end
end
end
end
- context 'and background upload enabled' do
+ context 'without the lfs object' do
before do
- stub_lfs_object_storage(background_upload: true)
+ lfs_object.destroy!
end
- it 'schedules migration of file to object storage' do
- expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('LfsObjectUploader', 'LfsObject', :file, kind_of(Numeric))
+ it 'rejects slashes in the tempfile name (path traversal)' do
+ put_finalize('../bar', with_tempfile: true)
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ context 'not sending the workhorse jwt header' do
+ let(:include_workhorse_jwt_header) { false }
- put_finalize(with_tempfile: true)
+ it 'rejects the request' do
+ put_finalize(with_tempfile: true)
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
end
end
end
- end
- context 'without the lfs object' do
- before do
- lfs_object.destroy!
- end
+ describe 'and user does not have push access' do
+ before do
+ project.add_reporter(user)
+ end
- it 'rejects slashes in the tempfile name (path traversal)' do
- put_finalize('../bar', with_tempfile: true)
- expect(response).to have_gitlab_http_status(:bad_request)
+ it_behaves_like 'forbidden'
end
+ end
- context 'not sending the workhorse jwt header' do
- let(:include_workhorse_jwt_header) { false }
-
- it 'rejects the request' do
- put_finalize(with_tempfile: true)
+ context 'when build is authorized' do
+ let(:authorization) { authorize_ci_project }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
- expect(response).to have_gitlab_http_status(:unprocessable_entity)
- end
- end
- end
- end
+ context 'build has an user' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
- describe 'and user does not have push access' do
- before do
- project.add_reporter(user)
- end
+ context 'tries to push to own project' do
+ before do
+ project.add_developer(user)
+ put_authorize
+ end
- it_behaves_like 'forbidden'
- end
- end
+ it_behaves_like 'LFS http 403 response'
+ end
- context 'when build is authorized' do
- let(:authorization) { authorize_ci_project }
+ context 'tries to push to other project' do
+ let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
- context 'build has an user' do
- let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+ before do
+ put_authorize
+ end
- context 'tries to push to own project' do
- before do
- project.add_developer(user)
- put_authorize
+ it_behaves_like 'LFS http 404 response'
+ end
end
- it_behaves_like 'LFS http 403 response'
- end
+ context 'does not have user' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline) }
- context 'tries to push to other project' do
- let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+ before do
+ put_authorize
+ end
- before do
- put_authorize
+ it_behaves_like 'LFS http 403 response'
end
-
- it_behaves_like 'LFS http 404 response'
end
- end
- context 'does not have user' do
- let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+ describe 'when using a user key (LFSToken)' do
+ let(:authorization) { authorize_user_key }
- before do
- put_authorize
- end
+ context 'when user allowed' do
+ before do
+ project.add_developer(user)
+ put_authorize
+ end
- it_behaves_like 'LFS http 403 response'
- end
- end
+ it_behaves_like 'LFS http 200 workhorse response'
- describe 'when using a user key (LFSToken)' do
- let(:authorization) { authorize_user_key }
+ context 'when user password is expired' do
+ let_it_be(:user) { create(:user, password_expires_at: 1.minute.ago)}
- context 'when user allowed' do
- before do
- project.add_developer(user)
- put_authorize
- end
+ it_behaves_like 'LFS http 401 response'
+ end
- it_behaves_like 'LFS http 200 workhorse response'
+ context 'when user is blocked' do
+ let_it_be(:user) { create(:user, :blocked)}
- context 'when user password is expired' do
- let(:user) { create(:user, password_expires_at: 1.minute.ago)}
+ it_behaves_like 'LFS http 401 response'
+ end
+ end
- it_behaves_like 'LFS http 401 response'
+ context 'when user not allowed' do
+ before do
+ put_authorize
+ end
+
+ it_behaves_like 'LFS http 404 response'
+ end
end
- context 'when user is blocked' do
- let(:user) { create(:user, :blocked)}
+ context 'for unauthenticated' do
+ let(:authorization) { nil }
- it_behaves_like 'LFS http 401 response'
+ it_behaves_like 'unauthorized'
end
end
- context 'when user not allowed' do
- before do
- put_authorize
- end
+ describe 'to a forked project' do
+ let_it_be(:upstream_project) { create(:project, :public) }
+ let_it_be(:project_owner) { create(:user) }
+ let(:project) { fork_project(upstream_project, project_owner) }
- it_behaves_like 'LFS http 404 response'
- end
- end
+ describe 'when user is authenticated' do
+ describe 'when user has push access to the project' do
+ before do
+ project.add_developer(user)
+ end
- context 'for unauthenticated' do
- it_behaves_like 'unauthorized'
- end
- end
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
- describe 'to a forked project' do
- let(:upstream_project) { create(:project, :public) }
- let(:project_owner) { create(:user) }
- let(:project) { fork_project(upstream_project, project_owner) }
+ it_behaves_like 'LFS http 200 workhorse response'
- describe 'when user is authenticated' do
- let(:authorization) { authorize_user }
+ it 'with location of LFS store and object details' do
+ expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
+ expect(json_response['LfsOid']).to eq(sample_oid)
+ expect(json_response['LfsSize']).to eq(sample_size)
+ end
+ end
- describe 'when user has push access to the project' do
- before do
- project.add_developer(user)
- end
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- put_authorize
- end
+ it_behaves_like 'LFS http 200 response'
- it_behaves_like 'LFS http 200 workhorse response'
+ it 'LFS object is linked to the forked project' do
+ expect(lfs_object.projects.pluck(:id)).to include(project.id)
+ end
+ end
+ end
- it 'with location of LFS store and object details' do
- expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
- expect(json_response['LfsOid']).to eq(sample_oid)
- expect(json_response['LfsSize']).to eq(sample_size)
+ describe 'and user does not have push access' do
+ it_behaves_like 'forbidden'
end
end
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ context 'when build is authorized' do
+ let(:authorization) { authorize_ci_project }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
before do
- put_finalize
+ put_authorize
end
- it_behaves_like 'LFS http 200 response'
+ context 'build has an user' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
- it 'LFS object is linked to the forked project' do
- expect(lfs_object.projects.pluck(:id)).to include(project.id)
- end
- end
- end
-
- describe 'and user does not have push access' do
- it_behaves_like 'forbidden'
- end
- end
+ context 'tries to push to own project' do
+ it_behaves_like 'LFS http 403 response'
+ end
- context 'when build is authorized' do
- let(:authorization) { authorize_ci_project }
+ context 'tries to push to other project' do
+ let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
- before do
- put_authorize
- end
+ # I'm not sure what this tests that is different from the previous test
+ it_behaves_like 'LFS http 403 response'
+ end
+ end
- context 'build has an user' do
- let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+ context 'does not have user' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline) }
- context 'tries to push to own project' do
- it_behaves_like 'LFS http 403 response'
+ it_behaves_like 'LFS http 403 response'
+ end
end
- context 'tries to push to other project' do
- let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+ context 'for unauthenticated' do
+ let(:authorization) { nil }
- # I'm not sure what this tests that is different from the previous test
- it_behaves_like 'LFS http 403 response'
+ it_behaves_like 'unauthorized'
end
- end
- context 'does not have user' do
- let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+ describe 'and second project not related to fork or a source project' do
+ let_it_be(:second_project) { create(:project) }
- it_behaves_like 'LFS http 403 response'
- end
- end
-
- context 'for unauthenticated' do
- it_behaves_like 'unauthorized'
- end
+ before do
+ second_project.add_maintainer(user)
+ upstream_project.lfs_objects << lfs_object
+ end
- describe 'and second project not related to fork or a source project' do
- let(:second_project) { create(:project) }
- let(:authorization) { authorize_user }
+ context 'when pushing the same LFS object to the second project' do
+ before do
+ finalize_headers = headers
+ .merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file)
+ .merge(workhorse_internal_api_request_header)
- before do
- second_project.add_maintainer(user)
- upstream_project.lfs_objects << lfs_object
- end
+ put objects_url(second_project, sample_oid, sample_size),
+ params: {},
+ headers: finalize_headers
+ end
- context 'when pushing the same LFS object to the second project' do
- before do
- finalize_headers = headers
- .merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file)
- .merge(workhorse_internal_api_request_header)
+ it_behaves_like 'LFS http 200 response'
- put objects_url(second_project, sample_oid, sample_size),
- params: {},
- headers: finalize_headers
+ it 'links the LFS object to the project' do
+ expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id)
+ end
+ end
end
+ end
- it_behaves_like 'LFS http 200 response'
+ def put_authorize(verified: true)
+ authorize_headers = headers
+ authorize_headers.merge!(workhorse_internal_api_request_header) if verified
- it 'links the LFS object to the project' do
- expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id)
- end
+ put authorize_url(project, sample_oid, sample_size), params: {}, headers: authorize_headers
end
- end
- end
- def put_authorize(verified: true)
- authorize_headers = headers
- authorize_headers.merge!(workhorse_internal_api_request_header) if verified
+ def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, verified: true, remote_object: nil, args: {})
+ uploaded_file = nil
- put authorize_url(project, sample_oid, sample_size), params: {}, headers: authorize_headers
- end
+ if with_tempfile
+ upload_path = LfsObjectUploader.workhorse_local_upload_path
+ file_path = upload_path + '/' + lfs_tmp if lfs_tmp
- def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, verified: true, remote_object: nil, args: {})
- uploaded_file = nil
+ FileUtils.mkdir_p(upload_path)
+ FileUtils.touch(file_path)
- if with_tempfile
- upload_path = LfsObjectUploader.workhorse_local_upload_path
- file_path = upload_path + '/' + lfs_tmp if lfs_tmp
+ uploaded_file = UploadedFile.new(file_path, filename: File.basename(file_path))
+ elsif remote_object
+ uploaded_file = fog_to_uploaded_file(remote_object)
+ end
- FileUtils.mkdir_p(upload_path)
- FileUtils.touch(file_path)
+ finalize_headers = headers
+ finalize_headers.merge!(workhorse_internal_api_request_header) if verified
+
+ workhorse_finalize(
+ objects_url(project, sample_oid, sample_size),
+ method: :put,
+ file_key: :file,
+ params: args.merge(file: uploaded_file),
+ headers: finalize_headers,
+ send_rewritten_field: include_workhorse_jwt_header
+ )
+ end
- uploaded_file = UploadedFile.new(file_path, filename: File.basename(file_path))
- elsif remote_object
- uploaded_file = fog_to_uploaded_file(remote_object)
+ def lfs_tmp_file
+ "#{sample_oid}012345678"
+ end
end
-
- finalize_headers = headers
- finalize_headers.merge!(workhorse_internal_api_request_header) if verified
-
- workhorse_finalize(
- objects_url(project, sample_oid, sample_size),
- method: :put,
- file_key: :file,
- params: args.merge(file: uploaded_file),
- headers: finalize_headers,
- send_rewritten_field: include_workhorse_jwt_header
- )
- end
-
- def lfs_tmp_file
- "#{sample_oid}012345678"
- end
- end
-
- context 'with projects' do
- it_behaves_like 'LFS http requests' do
- let(:container) { project }
- let(:authorize_guest) { project.add_guest(user) }
- let(:authorize_download) { project.add_reporter(user) }
- let(:authorize_upload) { project.add_developer(user) }
end
end
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index 3f57b8ba67b..f8fa9459467 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -7,108 +7,115 @@ RSpec.describe 'value stream analytics events' do
let(:project) { create(:project, :repository, public_builds: false) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
- describe 'GET /:namespace/:project/value_stream_analytics/events/issues' do
- before do
- project.add_developer(user)
+ shared_examples 'value stream analytics events examples' do
+ describe 'GET /:namespace/:project/value_stream_analytics/events/issues' do
+ before do
+ project.add_developer(user)
- 3.times do |count|
- travel_to(Time.now + count.days) do
- create_cycle
+ 3.times do |count|
+ travel_to(Time.now + count.days) do
+ create_cycle
+ end
end
- end
-
- deploy_master(user, project)
-
- login_as(user)
- end
-
- it 'lists the issue events' do
- get project_cycle_analytics_issue_path(project, format: :json)
- first_issue_iid = project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
+ deploy_master(user, project)
- expect(json_response['events']).not_to be_empty
- expect(json_response['events'].first['iid']).to eq(first_issue_iid)
- end
+ login_as(user)
+ end
- it 'lists the plan events' do
- get project_cycle_analytics_plan_path(project, format: :json)
+ it 'lists the issue events' do
+ get project_cycle_analytics_issue_path(project, format: :json)
- first_issue_iid = project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
+ first_issue_iid = project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
- expect(json_response['events']).not_to be_empty
- expect(json_response['events'].first['iid']).to eq(first_issue_iid)
- end
+ expect(json_response['events']).not_to be_empty
+ expect(json_response['events'].first['iid']).to eq(first_issue_iid)
+ end
- it 'lists the code events' do
- get project_cycle_analytics_code_path(project, format: :json)
+ it 'lists the plan events' do
+ get project_cycle_analytics_plan_path(project, format: :json)
- expect(json_response['events']).not_to be_empty
+ first_issue_iid = project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
- first_mr_iid = project.merge_requests.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
+ expect(json_response['events']).not_to be_empty
+ expect(json_response['events'].first['iid']).to eq(first_issue_iid)
+ end
- expect(json_response['events'].first['iid']).to eq(first_mr_iid)
- end
+ it 'lists the code events' do
+ get project_cycle_analytics_code_path(project, format: :json)
- it 'lists the test events', :sidekiq_might_not_need_inline do
- get project_cycle_analytics_test_path(project, format: :json)
+ expect(json_response['events']).not_to be_empty
- expect(json_response['events']).not_to be_empty
- expect(json_response['events'].first['date']).not_to be_empty
- end
+ first_mr_iid = project.merge_requests.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
- it 'lists the review events' do
- get project_cycle_analytics_review_path(project, format: :json)
+ expect(json_response['events'].first['iid']).to eq(first_mr_iid)
+ end
- first_mr_iid = project.merge_requests.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
+ it 'lists the test events', :sidekiq_inline do
+ get project_cycle_analytics_test_path(project, format: :json)
- expect(json_response['events']).not_to be_empty
- expect(json_response['events'].first['iid']).to eq(first_mr_iid)
- end
+ expect(json_response['events']).not_to be_empty
+ expect(json_response['events'].first['date']).not_to be_empty
+ end
- it 'lists the staging events', :sidekiq_might_not_need_inline do
- get project_cycle_analytics_staging_path(project, format: :json)
+ it 'lists the review events' do
+ get project_cycle_analytics_review_path(project, format: :json)
- expect(json_response['events']).not_to be_empty
- expect(json_response['events'].first['date']).not_to be_empty
- end
+ first_mr_iid = project.merge_requests.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
- context 'specific branch' do
- it 'lists the test events', :sidekiq_might_not_need_inline do
- branch = project.merge_requests.first.source_branch
+ expect(json_response['events']).not_to be_empty
+ expect(json_response['events'].first['iid']).to eq(first_mr_iid)
+ end
- get project_cycle_analytics_test_path(project, format: :json, branch: branch)
+ it 'lists the staging events', :sidekiq_inline do
+ get project_cycle_analytics_staging_path(project, format: :json)
expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['date']).not_to be_empty
end
- end
- context 'with private project and builds' do
- before do
- project.members.last.update(access_level: Gitlab::Access::GUEST)
- end
+ context 'with private project and builds' do
+ before do
+ project.members.last.update(access_level: Gitlab::Access::GUEST)
+ end
- it 'does not list the test events' do
- get project_cycle_analytics_test_path(project, format: :json)
+ it 'does not list the test events' do
+ get project_cycle_analytics_test_path(project, format: :json)
- expect(response).to have_gitlab_http_status(:not_found)
- end
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
- it 'does not list the staging events' do
- get project_cycle_analytics_staging_path(project, format: :json)
+ it 'does not list the staging events' do
+ get project_cycle_analytics_staging_path(project, format: :json)
- expect(response).to have_gitlab_http_status(:not_found)
- end
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
- it 'lists the issue events' do
- get project_cycle_analytics_issue_path(project, format: :json)
+ it 'lists the issue events' do
+ get project_cycle_analytics_issue_path(project, format: :json)
- expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
end
end
end
+ describe 'when new_project_level_vsa_backend feature flag is off' do
+ before do
+ stub_feature_flags(new_project_level_vsa_backend: false, thing: project)
+ end
+
+ it_behaves_like 'value stream analytics events examples'
+ end
+
+ describe 'when new_project_level_vsa_backend feature flag is on' do
+ before do
+ stub_feature_flags(new_project_level_vsa_backend: true, thing: project)
+ end
+
+ it_behaves_like 'value stream analytics events examples'
+ end
+
def create_cycle
milestone = create(:milestone, project: project)
issue.update(milestone: milestone)
@@ -123,5 +130,7 @@ RSpec.describe 'value stream analytics events' do
merge_merge_requests_closing_issue(user, project, issue)
ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash)
+
+ mr.metrics.update!(latest_build_started_at: 1.hour.ago, latest_build_finished_at: Time.now)
end
end
diff --git a/spec/requests/projects/metrics_dashboard_spec.rb b/spec/requests/projects/metrics_dashboard_spec.rb
index 0a4100f2bf5..c248463faa3 100644
--- a/spec/requests/projects/metrics_dashboard_spec.rb
+++ b/spec/requests/projects/metrics_dashboard_spec.rb
@@ -70,7 +70,7 @@ RSpec.describe 'Projects::MetricsDashboardController' do
context 'when query param environment does not exist' do
it 'responds with 404' do
- send_request(environment: 99)
+ send_request(environment: non_existing_record_id)
expect(response).to have_gitlab_http_status(:not_found)
end
end
@@ -105,7 +105,7 @@ RSpec.describe 'Projects::MetricsDashboardController' do
context 'when query param environment does not exist' do
it 'responds with 404' do
- send_request(dashboard_path: dashboard_path, environment: 99)
+ send_request(dashboard_path: dashboard_path, environment: non_existing_record_id)
expect(response).to have_gitlab_http_status(:not_found)
end
end
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index 805ac5a9118..c2e68df2c40 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -106,7 +106,7 @@ RSpec.describe 'Rack Attack global throttles' do
let(:request_jobs_url) { '/api/v4/jobs/request' }
let(:runner) { create(:ci_runner) }
- it 'does not cont as unauthenticated' do
+ it 'does not count as unauthenticated' do
(1 + requests_per_period).times do
post request_jobs_url, params: { token: runner.token }
expect(response).to have_gitlab_http_status(:no_content)
@@ -114,6 +114,17 @@ RSpec.describe 'Rack Attack global throttles' do
end
end
+ context 'when the request is to a health endpoint' do
+ let(:health_endpoint) { '/-/metrics' }
+
+ it 'does not throttle the requests' do
+ (1 + requests_per_period).times do
+ get health_endpoint
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
it 'logs RackAttack info into structured logs' do
requests_per_period.times do
get url_that_does_not_require_authentication
@@ -133,6 +144,14 @@ RSpec.describe 'Rack Attack global throttles' do
get url_that_does_not_require_authentication
end
+
+ it_behaves_like 'tracking when dry-run mode is set' do
+ let(:throttle_name) { 'throttle_unauthenticated' }
+
+ def do_request
+ get url_that_does_not_require_authentication
+ end
+ end
end
context 'when the throttle is disabled' do
@@ -231,6 +250,10 @@ RSpec.describe 'Rack Attack global throttles' do
let(:post_params) { { user: { login: 'username', password: 'password' } } }
+ def do_request
+ post protected_path_that_does_not_require_authentication, params: post_params
+ end
+
before do
settings_to_set[:throttle_protected_paths_requests_per_period] = requests_per_period # 1
settings_to_set[:throttle_protected_paths_period_in_seconds] = period_in_seconds # 10_000
@@ -244,7 +267,7 @@ RSpec.describe 'Rack Attack global throttles' do
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
- post protected_path_that_does_not_require_authentication, params: post_params
+ do_request
expect(response).to have_gitlab_http_status(:ok)
end
end
@@ -258,12 +281,16 @@ RSpec.describe 'Rack Attack global throttles' do
it 'rejects requests over the rate limit' do
requests_per_period.times do
- post protected_path_that_does_not_require_authentication, params: post_params
+ do_request
expect(response).to have_gitlab_http_status(:ok)
end
expect_rejection { post protected_path_that_does_not_require_authentication, params: post_params }
end
+
+ it_behaves_like 'tracking when dry-run mode is set' do
+ let(:throttle_name) { 'throttle_unauthenticated_protected_paths' }
+ end
end
end
diff --git a/spec/requests/self_monitoring_project_spec.rb b/spec/requests/self_monitoring_project_spec.rb
index 5844a27da17..f7227f71b05 100644
--- a/spec/requests/self_monitoring_project_spec.rb
+++ b/spec/requests/self_monitoring_project_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe 'Self-Monitoring project requests' do
it_behaves_like 'not accessible to non-admin users'
- context 'with admin user' do
+ context 'with admin user', :enable_admin_mode do
before do
login_as(admin)
end
@@ -36,7 +36,7 @@ RSpec.describe 'Self-Monitoring project requests' do
it_behaves_like 'not accessible to non-admin users'
- context 'with admin user' do
+ context 'with admin user', :enable_admin_mode do
before do
login_as(admin)
end
@@ -116,7 +116,7 @@ RSpec.describe 'Self-Monitoring project requests' do
it_behaves_like 'not accessible to non-admin users'
- context 'with admin user' do
+ context 'with admin user', :enable_admin_mode do
before do
login_as(admin)
end
@@ -140,7 +140,7 @@ RSpec.describe 'Self-Monitoring project requests' do
it_behaves_like 'not accessible to non-admin users'
- context 'with admin user' do
+ context 'with admin user', :enable_admin_mode do
before do
login_as(admin)
end
diff --git a/spec/requests/whats_new_controller_spec.rb b/spec/requests/whats_new_controller_spec.rb
index c04a6b00a93..8005d38dbb0 100644
--- a/spec/requests/whats_new_controller_spec.rb
+++ b/spec/requests/whats_new_controller_spec.rb
@@ -4,29 +4,31 @@ require 'spec_helper'
RSpec.describe WhatsNewController do
describe 'whats_new_path' do
- context 'with whats_new_drawer feature enabled' do
- let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
+ let(:item) { double(:item) }
+ let(:highlights) { double(:highlight, items: [item], map: [item].map, next_page: 2) }
+ context 'with whats_new_drawer feature enabled' do
before do
stub_feature_flags(whats_new_drawer: true)
- allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
end
context 'with no page param' do
it 'responds with paginated data and headers' do
+ allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(highlights)
+ allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
+
get whats_new_path, xhr: true
- expect(response.body).to eq([{ title: "bright and sunshinin' day", release: "01.05" }].to_json)
+ expect(response.body).to eq(highlights.items.to_json)
expect(response.headers['X-Next-Page']).to eq(2)
end
end
context 'with page param' do
- it 'responds with paginated data and headers' do
- get whats_new_path(page: 2), xhr: true
+ it 'passes the page parameter' do
+ expect(ReleaseHighlight).to receive(:paginated).with(page: 2).and_call_original
- expect(response.body).to eq([{ title: 'bright' }].to_json)
- expect(response.headers['X-Next-Page']).to eq(3)
+ get whats_new_path(page: 2), xhr: true
end
it 'returns a 404 if page param is negative' do
@@ -34,13 +36,17 @@ RSpec.describe WhatsNewController do
expect(response).to have_gitlab_http_status(:not_found)
end
+ end
+
+ context 'with version param' do
+ it 'returns items without pagination headers' do
+ allow(ReleaseHighlight).to receive(:for_version).with(version: '42').and_return(highlights)
+ allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
+
+ get whats_new_path(version: 42), xhr: true
- context 'when there are no more paginated results' do
- it 'responds with nil X-Next-Page header' do
- get whats_new_path(page: 3), xhr: true
- expect(response.body).to eq([{ title: "It's gonna be a bright" }].to_json)
- expect(response.headers['X-Next-Page']).to be nil
- end
+ expect(response.body).to eq(highlights.items.to_json)
+ expect(response.headers['X-Next-Page']).to be_nil
end
end
end
diff --git a/spec/routing/git_http_routing_spec.rb b/spec/routing/git_http_routing_spec.rb
index e5216d99eb9..e3cc1440a9e 100644
--- a/spec/routing/git_http_routing_spec.rb
+++ b/spec/routing/git_http_routing_spec.rb
@@ -3,22 +3,60 @@
require 'spec_helper'
RSpec.describe 'git_http routing' do
- include RSpec::Rails::RequestExampleGroup
+ describe 'code repositories' do
+ it_behaves_like 'git repository routes' do
+ let(:path) { '/gitlab-org/gitlab-test.git' }
+ end
+ end
+
+ describe 'wiki repositories' do
+ context 'in project' do
+ let(:path) { '/gitlab-org/gitlab-test.wiki.git' }
+
+ it_behaves_like 'git repository routes'
+
+ describe 'redirects', type: :request do
+ let(:web_path) { '/gitlab-org/gitlab-test/-/wikis' }
+
+ it 'redirects namespace/project.wiki.git to the project wiki' do
+ expect(get(path)).to redirect_to(web_path)
+ end
- describe 'wiki.git routing', 'routing' do
- let(:wiki_path) { '/gitlab/gitlabhq/wikis' }
+ it 'preserves query parameters' do
+ expect(get("#{path}?foo=bar&baz=qux")).to redirect_to("#{web_path}?foo=bar&baz=qux")
+ end
- it 'redirects namespace/project.wiki.git to the project wiki' do
- expect(get('/gitlab/gitlabhq.wiki.git')).to redirect_to(wiki_path)
+ it 'only redirects when the format is .git' do
+ expect(get(path.delete_suffix('.git'))).not_to redirect_to(web_path)
+ expect(get(path.delete_suffix('.git') + '.json')).not_to redirect_to(web_path)
+ end
+ end
end
- it 'preserves query parameters' do
- expect(get('/gitlab/gitlabhq.wiki.git?foo=bar&baz=qux')).to redirect_to("#{wiki_path}?foo=bar&baz=qux")
+ context 'in toplevel group' do
+ it_behaves_like 'git repository routes' do
+ let(:path) { '/gitlab-org.wiki.git' }
+ end
+ end
+
+ context 'in child group' do
+ it_behaves_like 'git repository routes' do
+ let(:path) { '/gitlab-org/child.wiki.git' }
+ end
+ end
+ end
+
+ describe 'snippet repositories' do
+ context 'personal snippet' do
+ it_behaves_like 'git repository routes' do
+ let(:path) { '/snippets/123.git' }
+ end
end
- it 'only redirects when the format is .git' do
- expect(get('/gitlab/gitlabhq.wiki')).not_to redirect_to(wiki_path)
- expect(get('/gitlab/gitlabhq.wiki.json')).not_to redirect_to(wiki_path)
+ context 'project snippet' do
+ it_behaves_like 'git repository routes' do
+ let(:path) { '/gitlab-org/gitlab-test/snippets/123.git' }
+ end
end
end
end
diff --git a/spec/routing/group_routing_spec.rb b/spec/routing/group_routing_spec.rb
index f4d5ccc81b6..f171c2faf5e 100644
--- a/spec/routing/group_routing_spec.rb
+++ b/spec/routing/group_routing_spec.rb
@@ -81,6 +81,10 @@ RSpec.describe "Groups", "routing" do
end
describe 'dependency proxy for containers' do
+ it 'routes to #authenticate' do
+ expect(get('/v2')).to route_to('groups/dependency_proxy_auth#authenticate')
+ end
+
context 'image name without namespace' do
it 'routes to #manifest' do
expect(get('/v2/gitlabhq/dependency_proxy/containers/ruby/manifests/2.3.6'))
diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb
index 15d2f32de78..b1da2eaa33b 100644
--- a/spec/routing/import_routing_spec.rb
+++ b/spec/routing/import_routing_spec.rb
@@ -126,32 +126,6 @@ RSpec.describe Import::BitbucketServerController, 'routing' do
end
end
-# status_import_google_code GET /import/google_code/status(.:format) import/google_code#status
-# callback_import_google_code POST /import/google_code/callback(.:format) import/google_code#callback
-# jobs_import_google_code GET /import/google_code/jobs(.:format) import/google_code#jobs
-# new_user_map_import_google_code GET /import/google_code/user_map(.:format) import/google_code#new_user_map
-# create_user_map_import_google_code POST /import/google_code/user_map(.:format) import/google_code#create_user_map
-# import_google_code POST /import/google_code(.:format) import/google_code#create
-# new_import_google_code GET /import/google_code/new(.:format) import/google_code#new
-RSpec.describe Import::GoogleCodeController, 'routing' do
- it_behaves_like 'importer routing' do
- let(:except_actions) { [:callback] }
- let(:provider) { 'google_code' }
- end
-
- it 'to #callback' do
- expect(post("/import/google_code/callback")).to route_to("import/google_code#callback")
- end
-
- it 'to #new_user_map' do
- expect(get('/import/google_code/user_map')).to route_to('import/google_code#new_user_map')
- end
-
- it 'to #create_user_map' do
- expect(post('/import/google_code/user_map')).to route_to('import/google_code#create_user_map')
- end
-end
-
# status_import_fogbugz GET /import/fogbugz/status(.:format) import/fogbugz#status
# callback_import_fogbugz POST /import/fogbugz/callback(.:format) import/fogbugz#callback
# realtime_changes_import_fogbugz GET /import/fogbugz/realtime_changes(.:format) import/fogbugz#realtime_changes
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 76ccdf3237c..26ad1f14786 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-# user GET /users/:username/
+# user GET /:username
+# user_ssh_keys GET /:username.keys
+# user_gpg_keys GET /:username.gpg
# user_groups GET /users/:username/groups(.:format)
# user_projects GET /users/:username/projects(.:format)
# user_contributed_projects GET /users/:username/contributed(.:format)
@@ -16,6 +18,12 @@ RSpec.describe UsersController, "routing" do
expect(get("/User")).to route_to('users#show', username: 'User')
end
+ it "to #gpg_keys" do
+ allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
+
+ expect(get("/User.gpg")).to route_to('users#gpg_keys', username: 'User')
+ end
+
it "to #groups" do
expect(get("/users/User/groups")).to route_to('users#groups', username: 'User')
end
@@ -32,6 +40,13 @@ RSpec.describe UsersController, "routing" do
expect(get("/users/User/snippets")).to route_to('users#snippets', username: 'User')
end
+ # get all the ssh-keys of a user
+ it "to #ssh_keys" do
+ allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
+
+ expect(get("/User.keys")).to route_to('users#ssh_keys', username: 'User')
+ end
+
it "to #calendar" do
expect(get("/users/User/calendar")).to route_to('users#calendar', username: 'User')
end
@@ -54,10 +69,6 @@ RSpec.describe "Mounted Apps", "routing" do
it "to API" do
expect(get("/api/issues")).to be_routable
end
-
- it "to Grack" do
- expect(get("/gitlab/gitlabhq.git")).to be_routable
- end
end
# snippets GET /snippets(.:format) snippets#index
@@ -175,11 +186,23 @@ RSpec.describe Profiles::KeysController, "routing" do
it "to #destroy" do
expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1')
end
+end
- it "to #get_keys" do
- allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
+# keys GET /gpg_keys gpg_keys#index
+# key POST /gpg_keys gpg_keys#create
+# PUT /gpg_keys/:id gpg_keys#revoke
+# DELETE /gpg_keys/:id gpg_keys#desroy
+RSpec.describe Profiles::GpgKeysController, "routing" do
+ it "to #index" do
+ expect(get("/profile/gpg_keys")).to route_to('profiles/gpg_keys#index')
+ end
- expect(get("/foo.keys")).to route_to('profiles/keys#get_keys', username: 'foo')
+ it "to #create" do
+ expect(post("/profile/gpg_keys")).to route_to('profiles/gpg_keys#create')
+ end
+
+ it "to #destroy" do
+ expect(delete("/profile/gpg_keys/1")).to route_to('profiles/gpg_keys#destroy', id: '1')
end
end
diff --git a/spec/rubocop/cop/gitlab/policy_rule_boolean_spec.rb b/spec/rubocop/cop/gitlab/policy_rule_boolean_spec.rb
new file mode 100644
index 00000000000..6221d038512
--- /dev/null
+++ b/spec/rubocop/cop/gitlab/policy_rule_boolean_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/gitlab/policy_rule_boolean'
+
+RSpec.describe RuboCop::Cop::Gitlab::PolicyRuleBoolean, type: :rubocop do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'registers offense for &&' do
+ expect_offense(<<~SOURCE)
+ rule { conducts_electricity && batteries }.enable :light_bulb
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ && is not allowed within a rule block. Did you mean to use `&`?
+ SOURCE
+ end
+
+ it 'registers offense for ||' do
+ expect_offense(<<~SOURCE)
+ rule { conducts_electricity || batteries }.enable :light_bulb
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ || is not allowed within a rule block. Did you mean to use `|`?
+ SOURCE
+ end
+
+ it 'registers offense for if' do
+ expect_offense(<<~SOURCE)
+ rule { if conducts_electricity then can?(:magnetize) else batteries end }.enable :motor
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ if and ternary operators are not allowed within a rule block.
+ SOURCE
+ end
+
+ it 'registers offense for ternary operator' do
+ expect_offense(<<~SOURCE)
+ rule { conducts_electricity ? can?(:magnetize) : batteries }.enable :motor
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ if and ternary operators are not allowed within a rule block.
+ SOURCE
+ end
+
+ it 'registers no offense for &' do
+ expect_no_offenses(<<~SOURCE)
+ rule { conducts_electricity & batteries }.enable :light_bulb
+ SOURCE
+ end
+
+ it 'registers no offense for |' do
+ expect_no_offenses(<<~SOURCE)
+ rule { conducts_electricity | batteries }.enable :light_bulb
+ SOURCE
+ end
+end
diff --git a/spec/rubocop/cop/graphql/descriptions_spec.rb b/spec/rubocop/cop/graphql/descriptions_spec.rb
index 3b29cd2fbee..f4693057bcb 100644
--- a/spec/rubocop/cop/graphql/descriptions_spec.rb
+++ b/spec/rubocop/cop/graphql/descriptions_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe RuboCop::Cop::Graphql::Descriptions, type: :rubocop do
subject(:cop) { described_class.new }
context 'fields' do
- it 'adds an offense when there is no field description' do
+ it 'adds an offense when there is no description' do
inspect_source(<<~TYPE)
module Types
class FakeType < BaseObject
@@ -24,24 +24,37 @@ RSpec.describe RuboCop::Cop::Graphql::Descriptions, type: :rubocop do
expect(cop.offenses.size).to eq 1
end
- it 'does not add an offense for fields with a description' do
- expect_no_offenses(<<~TYPE.strip)
+ it 'adds an offense when description does not end in a period' do
+ inspect_source(<<~TYPE)
module Types
class FakeType < BaseObject
- graphql_name 'FakeTypeName'
-
- argument :a_thing,
+ field :a_thing,
GraphQL::STRING_TYPE,
null: false,
description: 'A descriptive description'
end
end
TYPE
+
+ expect(cop.offenses.size).to eq 1
+ end
+
+ it 'does not add an offense when description is correct' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'A descriptive description.'
+ end
+ end
+ TYPE
end
end
context 'arguments' do
- it 'adds an offense when there is no argument description' do
+ it 'adds an offense when there is no description' do
inspect_source(<<~TYPE)
module Types
class FakeType < BaseObject
@@ -55,19 +68,88 @@ RSpec.describe RuboCop::Cop::Graphql::Descriptions, type: :rubocop do
expect(cop.offenses.size).to eq 1
end
- it 'does not add an offense for arguments with a description' do
- expect_no_offenses(<<~TYPE.strip)
+ it 'adds an offense when description does not end in a period' do
+ inspect_source(<<~TYPE)
module Types
class FakeType < BaseObject
- graphql_name 'FakeTypeName'
+ argument :a_thing,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'Behold! A description'
+ end
+ end
+ TYPE
+ expect(cop.offenses.size).to eq 1
+ end
+
+ it 'does not add an offense when description is correct' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class FakeType < BaseObject
argument :a_thing,
GraphQL::STRING_TYPE,
null: false,
+ description: 'Behold! A description.'
+ end
+ end
+ TYPE
+ end
+ end
+
+ describe 'autocorrecting descriptions without periods' do
+ it 'can autocorrect' do
+ expect_offense(<<~TYPE)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing,
+ ^^^^^^^^^^^^^^^ `description` strings must end with a `.`.
+ GraphQL::STRING_TYPE,
+ null: false,
description: 'Behold! A description'
end
end
TYPE
+
+ expect_correction(<<~TYPE)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'Behold! A description.'
+ end
+ end
+ TYPE
+ end
+
+ it 'can autocorrect a heredoc' do
+ expect_offense(<<~TYPE)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing,
+ ^^^^^^^^^^^^^^^ `description` strings must end with a `.`.
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: <<~DESC
+ Behold! A description
+ DESC
+ end
+ end
+ TYPE
+
+ expect_correction(<<~TYPE)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: <<~DESC
+ Behold! A description.
+ DESC
+ end
+ end
+ TYPE
end
end
end
diff --git a/spec/rubocop/cop/include_sidekiq_worker_spec.rb b/spec/rubocop/cop/include_sidekiq_worker_spec.rb
index 8d056c6a13e..33737babee5 100644
--- a/spec/rubocop/cop/include_sidekiq_worker_spec.rb
+++ b/spec/rubocop/cop/include_sidekiq_worker_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe RuboCop::Cop::IncludeSidekiqWorker, type: :rubocop do
let(:source) { 'include Sidekiq::Worker' }
let(:correct_source) { 'include ApplicationWorker' }
- it 'registers an offense ' do
+ it 'registers an offense' do
inspect_source(source)
aggregate_failures do
diff --git a/spec/rubocop/cop/lint/last_keyword_argument_spec.rb b/spec/rubocop/cop/lint/last_keyword_argument_spec.rb
new file mode 100644
index 00000000000..5822bf74e8d
--- /dev/null
+++ b/spec/rubocop/cop/lint/last_keyword_argument_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rubocop'
+require_relative '../../../../rubocop/cop/lint/last_keyword_argument'
+
+RSpec.describe RuboCop::Cop::Lint::LastKeywordArgument, type: :rubocop do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ before do
+ described_class.instance_variable_set(:@keyword_warnings, nil)
+ end
+
+ context 'deprecation files does not exist' do
+ before do
+ allow(Dir).to receive(:glob).and_return([])
+ allow(File).to receive(:exist?).and_return(false)
+ end
+
+ it 'does not register an offense' do
+ expect_no_offenses(<<~SOURCE)
+ users.call(params)
+ SOURCE
+ end
+ end
+
+ context 'deprecation files does exist' do
+ let(:create_spec_yaml) do
+ <<~YAML
+ ---
+ test_mutations/boards/lists/create#resolve_with_proper_permissions_backlog_list_creates_one_and_only_one_backlog:
+ - |
+ DEPRECATION WARNING: /Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/batch-loader-1.4.0/lib/batch_loader/graphql.rb:38: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
+ /Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/batch-loader-1.4.0/lib/batch_loader.rb:26: warning: The called method `batch' is defined here
+ test_mutations/boards/lists/create#ready?_raises_an_error_if_required_arguments_are_missing:
+ - |
+ DEPRECATION WARNING: /Users/tkuah/code/ee-gdk/gitlab/create_service.rb:1: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
+ /Users/tkuah/code/ee-gdk/gitlab/user.rb:17: warning: The called method `call' is defined here
+ YAML
+ end
+
+ let(:projects_spec_yaml) do
+ <<~YAML
+ ---
+ test_api/projects_get_/projects_when_unauthenticated_behaves_like_projects_response_returns_an_array_of_projects:
+ - |
+ DEPRECATION WARNING: /Users/tkuah/code/ee-gdk/gitlab/projects_spec.rb:1: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
+ /Users/tkuah/code/ee-gdk/gitlab/lib/gitlab/project.rb:15: warning: The called method `initialize' is defined here
+ - |
+ DEPRECATION WARNING: /Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/state_machines-activerecord-0.6.0/lib/state_machines/integrations/active_record.rb:511: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
+ /Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/activerecord-6.0.3.3/lib/active_record/suppressor.rb:43: warning: The called method `save' is defined here
+ - |
+ DEPRECATION WARNING: /Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/rack-2.2.3/lib/rack/builder.rb:158: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
+ /Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/grape-1.4.0/lib/grape/middleware/error.rb:30: warning: The called method `initialize' is defined here
+ YAML
+ end
+
+ before do
+ allow(Dir).to receive(:glob).and_return(['deprecations/service/create_spec.yml', 'deprecations/api/projects_spec.yml'])
+ allow(File).to receive(:read).and_return(create_spec_yaml, projects_spec_yaml)
+ end
+
+ it 'registers an offense' do
+ expect_offense(<<~SOURCE, 'create_service.rb')
+ users.call(params)
+ ^^^^^^ Using the last argument as keyword parameters is deprecated
+ SOURCE
+
+ expect_correction(<<~SOURCE)
+ users.call(**params)
+ SOURCE
+ end
+
+ it 'registers an offense for the new method call' do
+ expect_offense(<<~SOURCE, 'projects_spec.rb')
+ Project.new(params)
+ ^^^^^^ Using the last argument as keyword parameters is deprecated
+ SOURCE
+
+ expect_correction(<<~SOURCE)
+ Project.new(**params)
+ SOURCE
+ end
+
+ it 'registers an offense and corrects by converting hash to kwarg' do
+ expect_offense(<<~SOURCE, 'create_service.rb')
+ users.call(id, { a: :b, c: :d })
+ ^^^^^^^^^^^^^^^^ Using the last argument as keyword parameters is deprecated
+ SOURCE
+
+ expect_correction(<<~SOURCE)
+ users.call(id, a: :b, c: :d)
+ SOURCE
+ end
+
+ it 'registers an offense and corrects by converting splat to double splat' do
+ expect_offense(<<~SOURCE, 'create_service.rb')
+ users.call(id, *params)
+ ^^^^^^^ Using the last argument as keyword parameters is deprecated
+ SOURCE
+
+ expect_correction(<<~SOURCE)
+ users.call(id, **params)
+ SOURCE
+ end
+
+ it 'does not register an offense if already a kwarg', :aggregate_failures do
+ expect_no_offenses(<<~SOURCE, 'create_service.rb')
+ users.call(**params)
+ SOURCE
+
+ expect_no_offenses(<<~SOURCE, 'create_service.rb')
+ users.call(id, a: :b, c: :d)
+ SOURCE
+ end
+
+ it 'does not register an offense if the method name does not match' do
+ expect_no_offenses(<<~SOURCE, 'create_service.rb')
+ users.process(params)
+ SOURCE
+ end
+
+ it 'does not register an offense if the line number does not match' do
+ expect_no_offenses(<<~SOURCE, 'create_service.rb')
+ users.process
+ users.call(params)
+ SOURCE
+ end
+
+ it 'does not register an offense if the filename does not match' do
+ expect_no_offenses(<<~SOURCE, 'update_service.rb')
+ users.call(params)
+ SOURCE
+ end
+ end
+end
diff --git a/spec/rubocop/cop/migration/add_column_with_default_spec.rb b/spec/rubocop/cop/migration/add_column_with_default_spec.rb
index 50af344e0d4..6deb092f235 100644
--- a/spec/rubocop/cop/migration/add_column_with_default_spec.rb
+++ b/spec/rubocop/cop/migration/add_column_with_default_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe RuboCop::Cop::Migration::AddColumnWithDefault, type: :rubocop do
let(:offense) { '`add_column_with_default` is deprecated, use `add_column` instead' }
- it 'registers an offense ' do
+ it 'registers an offense' do
expect_offense(<<~RUBY)
def up
add_column_with_default(:merge_request_diff_files, :artifacts, :boolean, default: true, allow_null: false)
diff --git a/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb b/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb
index 93f43b0feb0..eaaa50b8190 100644
--- a/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb
+++ b/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb
@@ -88,7 +88,7 @@ RSpec.describe RuboCop::Cop::Migration::CreateTableWithForeignKeys, type: :ruboc
shared_examples 'target to high traffic table' do |dsl_method, table_name|
context 'when the target is defined as option' do
- it 'registers an offense ' do
+ it 'registers an offense' do
expect_offense(<<~RUBY)
def up
create_table(:foo) do |t|
@@ -102,7 +102,7 @@ RSpec.describe RuboCop::Cop::Migration::CreateTableWithForeignKeys, type: :ruboc
end
context 'when the target has implicit definition' do
- it 'registers an offense ' do
+ it 'registers an offense' do
expect_offense(<<~RUBY)
def up
create_table(:foo) do |t|
diff --git a/spec/rubocop/cop/rspec/htt_party_basic_auth_spec.rb b/spec/rubocop/cop/rspec/htt_party_basic_auth_spec.rb
new file mode 100644
index 00000000000..8c3703a488a
--- /dev/null
+++ b/spec/rubocop/cop/rspec/htt_party_basic_auth_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require_relative '../../../../rubocop/cop/rspec/httparty_basic_auth'
+
+RSpec.describe RuboCop::Cop::RSpec::HTTPartyBasicAuth, type: :rubocop do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when passing `basic_auth: { user: ... }`' do
+ it 'registers an offence' do
+ expect_offense(<<~SOURCE, 'spec/foo.rb')
+ HTTParty.put(
+ url,
+ basic_auth: { user: user, password: token },
+ ^^^^ #{described_class::MESSAGE}
+ body: body
+ )
+ SOURCE
+ end
+
+ it 'can autocorrect the source' do
+ bad = 'HTTParty.put(url, basic_auth: { user: user, password: token })'
+ good = 'HTTParty.put(url, basic_auth: { username: user, password: token })'
+ expect(autocorrect_source(bad)).to eq(good)
+ end
+ end
+
+ context 'when passing `basic_auth: { username: ... }`' do
+ it 'does not register an offence' do
+ expect_no_offenses(<<~SOURCE, 'spec/frontend/fixtures/foo.rb')
+ HTTParty.put(
+ url,
+ basic_auth: { username: user, password: token },
+ body: body
+ )
+ SOURCE
+ end
+ end
+end
diff --git a/spec/serializers/accessibility_reports_comparer_entity_spec.rb b/spec/serializers/accessibility_reports_comparer_entity_spec.rb
index c576dfa4dd1..dade2387ea9 100644
--- a/spec/serializers/accessibility_reports_comparer_entity_spec.rb
+++ b/spec/serializers/accessibility_reports_comparer_entity_spec.rb
@@ -51,8 +51,8 @@ RSpec.describe AccessibilityReportsComparerEntity do
expect(subject[:status]).to eq(Gitlab::Ci::Reports::AccessibilityReportsComparer::STATUS_FAILED)
expect(subject[:resolved_errors].first).to include(:code, :type, :type_code, :message, :context, :selector, :runner, :runner_extras)
expect(subject[:new_errors].first).to include(:code, :type, :type_code, :message, :context, :selector, :runner, :runner_extras)
- expect(subject[:existing_errors].first).to include(:code, :type, :type_code, :message, :context, :selector, :runner, :runner_extras)
- expect(subject[:summary]).to include(total: 2, resolved: 1, errored: 1)
+ expect(subject[:existing_errors]).to be_empty
+ expect(subject[:summary]).to include(total: 1, resolved: 1, errored: 1)
end
end
diff --git a/spec/serializers/admin/user_entity_spec.rb b/spec/serializers/admin/user_entity_spec.rb
new file mode 100644
index 00000000000..7db49af09c3
--- /dev/null
+++ b/spec/serializers/admin/user_entity_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Admin::UserEntity do
+ let_it_be(:user) { build_stubbed(:user) }
+ let(:request) { double('request') }
+
+ let(:entity) do
+ described_class.new(user, request: request)
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json&.keys }
+
+ it 'exposes correct attributes' do
+ is_expected.to contain_exactly(
+ :id,
+ :name,
+ :created_at,
+ :email,
+ :username,
+ :last_activity_on,
+ :avatar_url,
+ :badges,
+ :projects_count,
+ :actions
+ )
+ end
+ end
+end
diff --git a/spec/serializers/admin/user_serializer_spec.rb b/spec/serializers/admin/user_serializer_spec.rb
new file mode 100644
index 00000000000..719a90384c6
--- /dev/null
+++ b/spec/serializers/admin/user_serializer_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Admin::UserSerializer do
+ let(:resource) { build(:user) }
+
+ subject { described_class.new.represent(resource).keys }
+
+ context 'when there is a single object provided' do
+ it 'contains important elements for the admin user table' do
+ is_expected.to contain_exactly(
+ :id,
+ :name,
+ :created_at,
+ :email,
+ :username,
+ :last_activity_on,
+ :avatar_url,
+ :badges,
+ :projects_count,
+ :actions
+ )
+ end
+ end
+end
diff --git a/spec/serializers/board_simple_entity_spec.rb b/spec/serializers/board_simple_entity_spec.rb
new file mode 100644
index 00000000000..c5ab9833adf
--- /dev/null
+++ b/spec/serializers/board_simple_entity_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BoardSimpleEntity do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:board) { create(:board, project: project) }
+
+ subject { described_class.new(board).as_json }
+
+ describe '#name' do
+ it 'has `name` attribute' do
+ is_expected.to include(:name)
+ end
+ end
+end
diff --git a/spec/serializers/codequality_degradation_entity_spec.rb b/spec/serializers/codequality_degradation_entity_spec.rb
new file mode 100644
index 00000000000..fc969195e35
--- /dev/null
+++ b/spec/serializers/codequality_degradation_entity_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CodequalityDegradationEntity do
+ let(:entity) { described_class.new(codequality_degradation) }
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ context 'when codequality contains an error' do
+ context 'when line is included in location' do
+ let(:codequality_degradation) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ it 'contains correct codequality degradation details', :aggregate_failures do
+ expect(subject[:description]).to eq("Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.")
+ expect(subject[:severity]).to eq("major")
+ expect(subject[:file_path]).to eq("foo.rb")
+ expect(subject[:line]).to eq(10)
+ end
+ end
+
+ context 'when line is included in positions' do
+ let(:codequality_degradation) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 24,
+ "line": 14
+ },
+ "end": {
+ "column": 49,
+ "line": 14
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ it 'contains correct codequality degradation details', :aggregate_failures do
+ expect(subject[:description]).to eq("Avoid parameter lists longer than 5 parameters. [12/5]")
+ expect(subject[:severity]).to eq("minor")
+ expect(subject[:file_path]).to eq("foo.rb")
+ expect(subject[:line]).to eq(14)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/serializers/codequality_reports_comparer_entity_spec.rb b/spec/serializers/codequality_reports_comparer_entity_spec.rb
new file mode 100644
index 00000000000..7a79c30cc3f
--- /dev/null
+++ b/spec/serializers/codequality_reports_comparer_entity_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CodequalityReportsComparerEntity do
+ let(:entity) { described_class.new(comparer) }
+ let(:comparer) { Gitlab::Ci::Reports::CodequalityReportsComparer.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:degradation_1) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ let(:degradation_2) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 14,
+ "line": 10
+ },
+ "end": {
+ "column": 39,
+ "line": 10
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ context 'when base and head report have errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'contains correct compared codequality report details', :aggregate_failures do
+ expect(subject[:status]).to eq(Gitlab::Ci::Reports::CodequalityReportsComparer::STATUS_FAILED)
+ expect(subject[:resolved_errors].first).to include(:description, :severity, :file_path, :line)
+ expect(subject[:new_errors].first).to include(:description, :severity, :file_path, :line)
+ expect(subject[:existing_errors]).to be_empty
+ expect(subject[:summary]).to include(total: 1, resolved: 1, errored: 1)
+ end
+ end
+ end
+end
diff --git a/spec/serializers/codequality_reports_comparer_serializer_spec.rb b/spec/serializers/codequality_reports_comparer_serializer_spec.rb
new file mode 100644
index 00000000000..3c47bfb6adc
--- /dev/null
+++ b/spec/serializers/codequality_reports_comparer_serializer_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CodequalityReportsComparerSerializer do
+ let(:project) { double(:project) }
+ let(:serializer) { described_class.new(project: project).represent(comparer) }
+ let(:comparer) { Gitlab::Ci::Reports::CodequalityReportsComparer.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:degradation_1) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ let(:degradation_2) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 14,
+ "line": 10
+ },
+ "end": {
+ "column": 39,
+ "line": 10
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ describe '#to_json' do
+ subject { serializer.as_json }
+
+ context 'when base report has error and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'matches the schema' do
+ expect(subject).to match_schema('entities/codequality_reports_comparer')
+ end
+ end
+
+ context 'when base report has no error and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'matches the schema' do
+ expect(subject).to match_schema('entities/codequality_reports_comparer')
+ end
+ end
+ end
+end
diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb
index f5d6706a844..5b83507b4ec 100644
--- a/spec/serializers/environment_entity_spec.rb
+++ b/spec/serializers/environment_entity_spec.rb
@@ -7,15 +7,20 @@ RSpec.describe EnvironmentEntity do
let(:request) { double('request') }
let(:entity) do
- described_class.new(environment, request: spy('request'))
+ described_class.new(environment, request: request)
end
let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project) }
- let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:environment, refind: true) { create(:environment, project: project) }
+
+ before_all do
+ project.add_developer(user)
+ end
before do
- allow(entity).to receive(:current_user).and_return(user)
+ allow(request).to receive(:current_user).and_return(user)
+ allow(request).to receive(:project).and_return(project)
end
subject { entity.as_json }
@@ -32,6 +37,51 @@ RSpec.describe EnvironmentEntity do
expect(subject).to include(:folder_path)
end
+ context 'when there is a successful deployment' do
+ let!(:pipeline) { create(:ci_pipeline, :success, project: project) }
+ let!(:deployable) { create(:ci_build, :success, project: project, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, :success, project: project, environment: environment, deployable: deployable) }
+
+ it 'exposes it as the latest deployment' do
+ expect(subject[:last_deployment][:sha]).to eq(deployment.sha)
+ end
+
+ it 'does not expose it as an upcoming deployment' do
+ expect(subject[:upcoming_deployment]).to be_nil
+ end
+
+ context 'when the deployment pipeline has the other manual job' do
+ let!(:manual_job) { create(:ci_build, :manual, name: 'stop-review', project: project, pipeline: pipeline) }
+
+ it 'exposes the manual job in the latest deployment' do
+ expect(subject[:last_deployment][:manual_actions].first[:name])
+ .to eq(manual_job.name)
+ end
+ end
+ end
+
+ context 'when there is a running deployment' do
+ let!(:pipeline) { create(:ci_pipeline, :running, project: project) }
+ let!(:deployable) { create(:ci_build, :running, project: project, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, :running, project: project, environment: environment, deployable: deployable) }
+
+ it 'does not expose it as the latest deployment' do
+ expect(subject[:last_deployment]).to be_nil
+ end
+
+ it 'exposes it as an upcoming deployment' do
+ expect(subject[:upcoming_deployment][:sha]).to eq(deployment.sha)
+ end
+
+ context 'when the deployment pipeline has the other manual job' do
+ let!(:manual_job) { create(:ci_build, :manual, name: 'stop-review', project: project, pipeline: pipeline) }
+
+ it 'does not expose the manual job in the latest deployment' do
+ expect(subject[:upcoming_deployment][:manual_actions]).to be_nil
+ end
+ end
+ end
+
context 'metrics disabled' do
before do
allow(environment).to receive(:has_metrics?).and_return(false)
diff --git a/spec/serializers/import/bulk_import_entity_spec.rb b/spec/serializers/import/bulk_import_entity_spec.rb
index f35684bef20..3dfc659daf7 100644
--- a/spec/serializers/import/bulk_import_entity_spec.rb
+++ b/spec/serializers/import/bulk_import_entity_spec.rb
@@ -7,14 +7,15 @@ RSpec.describe Import::BulkImportEntity do
{
'id' => 1,
'full_name' => 'test',
- 'full_path' => 'full/path/test',
+ 'full_path' => 'full/path/tes',
+ 'web_url' => 'http://web.url/path',
'foo' => 'bar'
}
end
subject { described_class.represent(importable_data).as_json }
- %w[id full_name full_path].each do |attribute|
+ %w[id full_name full_path web_url].each do |attribute|
it "exposes #{attribute}" do
expect(subject[attribute.to_sym]).to eq(importable_data[attribute])
end
diff --git a/spec/serializers/merge_request_current_user_entity_spec.rb b/spec/serializers/merge_request_current_user_entity_spec.rb
new file mode 100644
index 00000000000..d4cbfe726c1
--- /dev/null
+++ b/spec/serializers/merge_request_current_user_entity_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequestCurrentUserEntity do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:request) { EntityRequest.new(project: project, current_user: user) }
+
+ let(:entity) do
+ described_class.new(user, request: request)
+ end
+
+ context 'as json' do
+ subject { entity.as_json }
+
+ it 'exposes needed attributes' do
+ expect(subject).to include(:can_fork, :can_create_merge_request, :fork_path)
+ end
+ end
+end
diff --git a/spec/serializers/merge_request_user_entity_spec.rb b/spec/serializers/merge_request_user_entity_spec.rb
index 8d6f066481e..a2ad8e72845 100644
--- a/spec/serializers/merge_request_user_entity_spec.rb
+++ b/spec/serializers/merge_request_user_entity_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe MergeRequestUserEntity do
subject { entity.as_json }
it 'exposes needed attributes' do
- expect(subject).to include(:can_fork, :can_create_merge_request, :fork_path)
+ expect(subject).to include(:id, :name, :username, :state, :avatar_url, :web_url, :can_merge)
end
end
end
diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb
index 3f7d5542ae8..9f734c08ef4 100644
--- a/spec/serializers/merge_request_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_entity_spec.rb
@@ -285,54 +285,34 @@ RSpec.describe MergeRequestWidgetEntity do
end
describe 'user callouts' do
- context 'when suggest pipeline feature is enabled' do
- subject { described_class.new(resource, request: request, experiment_enabled: :suggest_pipeline).as_json }
+ subject { described_class.new(resource, request: request).as_json }
- it 'provides a valid path value for user callout path' do
- expect(subject[:user_callouts_path]).to eq '/-/user_callouts'
- end
-
- it 'provides a valid value for suggest pipeline feature id' do
- expect(subject[:suggest_pipeline_feature_id]).to eq described_class::SUGGEST_PIPELINE
- end
-
- it 'provides a valid value for if it is dismissed' do
- expect(subject[:is_dismissed_suggest_pipeline]).to be(false)
- end
-
- context 'when the suggest pipeline has been dismissed' do
- before do
- create(:user_callout, user: user, feature_name: described_class::SUGGEST_PIPELINE)
- end
-
- it 'indicates suggest pipeline has been dismissed' do
- expect(subject[:is_dismissed_suggest_pipeline]).to be(true)
- end
- end
+ it 'provides a valid path value for user callout path' do
+ expect(subject[:user_callouts_path]).to eq '/-/user_callouts'
+ end
- context 'when user is not logged in' do
- let(:request) { double('request', current_user: nil, project: project) }
+ it 'provides a valid value for suggest pipeline feature id' do
+ expect(subject[:suggest_pipeline_feature_id]).to eq described_class::SUGGEST_PIPELINE
+ end
- it 'returns a blank is dismissed value' do
- expect(subject[:is_dismissed_suggest_pipeline]).to be_nil
- end
- end
+ it 'provides a valid value for if it is dismissed' do
+ expect(subject[:is_dismissed_suggest_pipeline]).to be(false)
end
- context 'when suggest pipeline feature is not enabled' do
+ context 'when the suggest pipeline has been dismissed' do
before do
- stub_feature_flags(suggest_pipeline: false)
+ create(:user_callout, user: user, feature_name: described_class::SUGGEST_PIPELINE)
end
- it 'provides no valid value for user callout path' do
- expect(subject[:user_callouts_path]).to be_nil
+ it 'indicates suggest pipeline has been dismissed' do
+ expect(subject[:is_dismissed_suggest_pipeline]).to be(true)
end
+ end
- it 'provides no valid value for suggest pipeline feature id' do
- expect(subject[:suggest_pipeline_feature_id]).to be_nil
- end
+ context 'when user is not logged in' do
+ let(:request) { double('request', current_user: nil, project: project) }
- it 'provides no valid value for if it is dismissed' do
+ it 'returns a blank is dismissed value' do
expect(subject[:is_dismissed_suggest_pipeline]).to be_nil
end
end
@@ -371,4 +351,18 @@ RSpec.describe MergeRequestWidgetEntity do
it 'has security_reports_docs_path' do
expect(subject[:security_reports_docs_path]).not_to be_nil
end
+
+ describe 'has source_project_default_url' do
+ it 'returns the default url to the source project' do
+ expect(subject[:source_project_default_url]).to eq project.http_url_to_repo
+ end
+
+ context 'when source project is nil' do
+ it 'returns nil' do
+ allow(resource).to receive(:source_project).and_return(nil)
+
+ expect(subject[:source_project_default_url]).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/serializers/paginated_diff_entity_spec.rb b/spec/serializers/paginated_diff_entity_spec.rb
index 551b392c9e9..7090ce1f08d 100644
--- a/spec/serializers/paginated_diff_entity_spec.rb
+++ b/spec/serializers/paginated_diff_entity_spec.rb
@@ -19,6 +19,10 @@ RSpec.describe PaginatedDiffEntity do
subject { entity.as_json }
+ before do
+ stub_feature_flags(diffs_gradual_load: false)
+ end
+
it 'exposes diff_files' do
expect(subject[:diff_files]).to be_present
end
diff --git a/spec/serializers/rollout_status_entity_spec.rb b/spec/serializers/rollout_status_entity_spec.rb
new file mode 100644
index 00000000000..7ad4b259bcd
--- /dev/null
+++ b/spec/serializers/rollout_status_entity_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe RolloutStatusEntity do
+ include KubernetesHelpers
+
+ let(:rollout_status) { kube_deployment_rollout_status }
+
+ let(:entity) do
+ described_class.new(rollout_status, request: double)
+ end
+
+ subject { entity.as_json }
+
+ it "exposes status" do
+ is_expected.to include(:status)
+ end
+
+ it 'exposes has_legacy_app_label' do
+ is_expected.to include(:has_legacy_app_label)
+ end
+
+ context 'when kube deployment is valid' do
+ it "exposes deployment data" do
+ is_expected.to include(:instances, :completion, :is_completed)
+ end
+
+ it 'does not expose canary ingress if it does not exist' do
+ is_expected.not_to include(:canary_ingress)
+ end
+
+ context 'when canary ingress exists' do
+ let(:rollout_status) { kube_deployment_rollout_status(ingresses: [kube_ingress(track: :canary)]) }
+
+ it 'expose canary ingress' do
+ is_expected.to include(:canary_ingress)
+ end
+ end
+ end
+
+ context 'when kube deployment is empty' do
+ let(:rollout_status) { empty_deployment_rollout_status }
+
+ it "exposes status" do
+ is_expected.to include(:status)
+ end
+
+ it "does not expose deployment data" do
+ is_expected.not_to include(:instances, :completion, :is_completed, :canary_ingress)
+ end
+ end
+end
diff --git a/spec/serializers/rollout_statuses/ingress_entity_spec.rb b/spec/serializers/rollout_statuses/ingress_entity_spec.rb
new file mode 100644
index 00000000000..b87b9e5c6c4
--- /dev/null
+++ b/spec/serializers/rollout_statuses/ingress_entity_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe RolloutStatuses::IngressEntity do
+ include KubernetesHelpers
+
+ let(:canary_ingress) { kube_ingress(track: :canary) }
+
+ let(:entity) do
+ described_class.new(canary_ingress, request: double)
+ end
+
+ subject { entity.as_json }
+
+ it 'exposes canary weight' do
+ is_expected.to include(:canary_weight)
+ end
+end
diff --git a/spec/services/alert_management/process_prometheus_alert_service_spec.rb b/spec/services/alert_management/process_prometheus_alert_service_spec.rb
index 2f920de7fc7..8f81c1967d5 100644
--- a/spec/services/alert_management/process_prometheus_alert_service_spec.rb
+++ b/spec/services/alert_management/process_prometheus_alert_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
end
describe '#execute' do
- let(:service) { described_class.new(project, nil, payload) }
+ let(:service) { described_class.new(project, payload) }
let(:auto_close_incident) { true }
let(:create_issue) { true }
let(:send_email) { true }
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index 03e55b3ec46..46483fede97 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe ApplicationSettings::UpdateService do
expect(application_settings.terms).to eq('Be nice!')
end
- it 'Only queries once when the terms are changed' do
+ it 'only queries once when the terms are changed' do
create(:term, terms: 'Other terms')
expect(application_settings.terms).to eq('Other terms')
@@ -257,7 +257,7 @@ RSpec.describe ApplicationSettings::UpdateService do
described_class.new(application_settings, admin, { external_authorization_service_enabled: false }).execute
end
- it 'does validate labels if external authorization gets enabled ' do
+ it 'does validate labels if external authorization gets enabled' do
expect_any_instance_of(described_class).to receive(:validate_classification_label)
described_class.new(application_settings, admin, { external_authorization_service_enabled: true }).execute
@@ -350,4 +350,28 @@ RSpec.describe ApplicationSettings::UpdateService do
expect(application_settings.issues_create_limit).to eq(600)
end
end
+
+ context 'when require_admin_approval_after_user_signup changes' do
+ context 'when it goes from enabled to disabled' do
+ let(:params) { { require_admin_approval_after_user_signup: false } }
+
+ it 'calls ApproveBlockedPendingApprovalUsersWorker' do
+ expect(ApproveBlockedPendingApprovalUsersWorker).to receive(:perform_async)
+
+ subject.execute
+ end
+ end
+
+ context 'when it goes from disabled to enabled' do
+ let(:params) { { require_admin_approval_after_user_signup: true } }
+
+ it 'does not call ApproveBlockedPendingApprovalUsersWorker' do
+ application_settings.update!(require_admin_approval_after_user_signup: false)
+
+ expect(ApproveBlockedPendingApprovalUsersWorker).not_to receive(:perform_async)
+
+ subject.execute
+ end
+ end
+ end
end
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 90ef32f1c5c..ba7acd3d3df 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -5,993 +5,5 @@ require 'spec_helper'
RSpec.describe Auth::ContainerRegistryAuthenticationService do
include AdminModeHelper
- let(:current_project) { nil }
- let(:current_user) { nil }
- let(:current_params) { {} }
- let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
- let(:payload) { JWT.decode(subject[:token], rsa_key, true, { algorithm: 'RS256' }).first }
-
- let(:authentication_abilities) do
- [:read_container_image, :create_container_image, :admin_container_image]
- end
-
- subject do
- described_class.new(current_project, current_user, current_params)
- .execute(authentication_abilities: authentication_abilities)
- end
-
- before do
- allow(Gitlab.config.registry).to receive_messages(enabled: true, issuer: 'rspec', key: nil)
- allow_next_instance_of(JSONWebToken::RSAToken) do |instance|
- allow(instance).to receive(:key).and_return(rsa_key)
- end
- end
-
- shared_examples 'an authenticated' do
- it { is_expected.to include(:token) }
- it { expect(payload).to include('access') }
- end
-
- shared_examples 'a valid token' do
- it { is_expected.to include(:token) }
- it { expect(payload).to include('access') }
-
- context 'a expirable' do
- let(:expires_at) { Time.zone.at(payload['exp']) }
- let(:expire_delay) { 10 }
-
- context 'for default configuration' do
- it { expect(expires_at).not_to be_within(2.seconds).of(Time.current + expire_delay.minutes) }
- end
-
- context 'for changed configuration' do
- before do
- stub_application_setting(container_registry_token_expire_delay: expire_delay)
- end
-
- it { expect(expires_at).to be_within(2.seconds).of(Time.current + expire_delay.minutes) }
- end
- end
- end
-
- shared_examples 'a browsable' do
- let(:access) do
- [{ 'type' => 'registry',
- 'name' => 'catalog',
- 'actions' => ['*'] }]
- end
-
- it_behaves_like 'a valid token'
- it_behaves_like 'not a container repository factory'
-
- it 'has the correct scope' do
- expect(payload).to include('access' => access)
- end
- end
-
- shared_examples 'an accessible' do
- let(:access) do
- [{ 'type' => 'repository',
- 'name' => project.full_path,
- 'actions' => actions }]
- end
-
- it_behaves_like 'a valid token'
-
- it 'has the correct scope' do
- expect(payload).to include('access' => access)
- end
- end
-
- shared_examples 'an inaccessible' do
- it_behaves_like 'a valid token'
- it { expect(payload).to include('access' => []) }
- end
-
- shared_examples 'a deletable' do
- it_behaves_like 'an accessible' do
- let(:actions) { ['*'] }
- end
- end
-
- shared_examples 'a deletable since registry 2.7' do
- it_behaves_like 'an accessible' do
- let(:actions) { ['delete'] }
- end
- end
-
- shared_examples 'a pullable' do
- it_behaves_like 'an accessible' do
- let(:actions) { ['pull'] }
- end
- end
-
- shared_examples 'a pushable' do
- it_behaves_like 'an accessible' do
- let(:actions) { ['push'] }
- end
- end
-
- shared_examples 'a pullable and pushable' do
- it_behaves_like 'an accessible' do
- let(:actions) { %w(pull push) }
- end
- end
-
- shared_examples 'a forbidden' do
- it { is_expected.to include(http_status: 403) }
- it { is_expected.not_to include(:token) }
- end
-
- shared_examples 'container repository factory' do
- it 'creates a new container repository resource' do
- expect { subject }
- .to change { project.container_repositories.count }.by(1)
- end
- end
-
- shared_examples 'not a container repository factory' do
- it 'does not create a new container repository resource' do
- expect { subject }.not_to change { ContainerRepository.count }
- end
- end
-
- describe '#full_access_token' do
- let_it_be(:project) { create(:project) }
- let(:token) { described_class.full_access_token(project.full_path) }
-
- subject { { token: token } }
-
- it_behaves_like 'an accessible' do
- let(:actions) { ['*'] }
- end
-
- it_behaves_like 'not a container repository factory'
- end
-
- describe '#pull_access_token' do
- let_it_be(:project) { create(:project) }
- let(:token) { described_class.pull_access_token(project.full_path) }
-
- subject { { token: token } }
-
- it_behaves_like 'an accessible' do
- let(:actions) { ['pull'] }
- end
-
- it_behaves_like 'not a container repository factory'
- end
-
- context 'user authorization' do
- let_it_be(:current_user) { create(:user) }
-
- context 'for registry catalog' do
- let(:current_params) do
- { scopes: ["registry:catalog:*"] }
- end
-
- context 'disallow browsing for users without GitLab admin rights' do
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
- end
-
- context 'for private project' do
- let_it_be(:project) { create(:project) }
-
- context 'allow to use scope-less authentication' do
- it_behaves_like 'a valid token'
- end
-
- context 'allow developer to push images' do
- before_all do
- project.add_developer(current_user)
- end
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
- end
-
- it_behaves_like 'a pushable'
- it_behaves_like 'container repository factory'
- end
-
- context 'disallow developer to delete images' do
- before_all do
- project.add_developer(current_user)
- end
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:*"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
-
- it 'logs an auth warning' do
- expect(Gitlab::AuthLogger).to receive(:warn).with(
- message: 'Denied container registry permissions',
- scope_type: 'repository',
- requested_project_path: project.full_path,
- requested_actions: ['*'],
- authorized_actions: [],
- user_id: current_user.id,
- username: current_user.username
- )
-
- subject
- end
- end
-
- context 'disallow developer to delete images since registry 2.7' do
- before_all do
- project.add_developer(current_user)
- end
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:delete"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'allow reporter to pull images' do
- before_all do
- project.add_reporter(current_user)
- end
-
- context 'when pulling from root level repository' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull"] }
- end
-
- it_behaves_like 'a pullable'
- it_behaves_like 'not a container repository factory'
- end
- end
-
- context 'disallow reporter to delete images' do
- before_all do
- project.add_reporter(current_user)
- end
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:*"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow reporter to delete images since registry 2.7' do
- before_all do
- project.add_reporter(current_user)
- end
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:delete"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'return a least of privileges' do
- before_all do
- project.add_reporter(current_user)
- end
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push,pull"] }
- end
-
- it_behaves_like 'a pullable'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow guest to pull or push images' do
- before_all do
- project.add_guest(current_user)
- end
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull,push"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow guest to delete images' do
- before_all do
- project.add_guest(current_user)
- end
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:*"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow guest to delete images since registry 2.7' do
- before_all do
- project.add_guest(current_user)
- end
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:delete"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
- end
-
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
-
- context 'allow anyone to pull images' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull"] }
- end
-
- it_behaves_like 'a pullable'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow anyone to push images' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow anyone to delete images' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:*"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow anyone to delete images since registry 2.7' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:delete"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'when repository name is invalid' do
- let(:current_params) do
- { scopes: ['repository:invalid:push'] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
- end
-
- context 'for internal project' do
- let_it_be(:project) { create(:project, :internal) }
-
- context 'for internal user' do
- context 'allow anyone to pull images' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull"] }
- end
-
- it_behaves_like 'a pullable'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow anyone to push images' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow anyone to delete images' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:*"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow anyone to delete images since registry 2.7' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:delete"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
- end
-
- context 'for external user' do
- context 'disallow anyone to pull or push images' do
- let_it_be(:current_user) { create(:user, external: true) }
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull,push"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow anyone to delete images' do
- let_it_be(:current_user) { create(:user, external: true) }
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:*"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'disallow anyone to delete images since registry 2.7' do
- let_it_be(:current_user) { create(:user, external: true) }
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:delete"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
- end
- end
- end
-
- context 'delete authorized as maintainer' do
- let_it_be(:current_project) { create(:project) }
- let_it_be(:current_user) { create(:user) }
-
- let(:authentication_abilities) do
- [:admin_container_image]
- end
-
- before_all do
- current_project.add_maintainer(current_user)
- end
-
- it_behaves_like 'a valid token'
-
- context 'allow to delete images' do
- let(:current_params) do
- { scopes: ["repository:#{current_project.full_path}:*"] }
- end
-
- it_behaves_like 'a deletable' do
- let(:project) { current_project }
- end
- end
-
- context 'allow to delete images since registry 2.7' do
- let(:current_params) do
- { scopes: ["repository:#{current_project.full_path}:delete"] }
- end
-
- it_behaves_like 'a deletable since registry 2.7' do
- let(:project) { current_project }
- end
- end
- end
-
- context 'build authorized as user' do
- let_it_be(:current_project) { create(:project) }
- let_it_be(:current_user) { create(:user) }
-
- let(:authentication_abilities) do
- [:build_read_container_image, :build_create_container_image, :build_destroy_container_image]
- end
-
- before_all do
- current_project.add_developer(current_user)
- end
-
- context 'allow to use offline_token' do
- let(:current_params) do
- { offline_token: true }
- end
-
- it_behaves_like 'an authenticated'
- end
-
- it_behaves_like 'a valid token'
-
- context 'allow to pull and push images' do
- let(:current_params) do
- { scopes: ["repository:#{current_project.full_path}:pull,push"] }
- end
-
- it_behaves_like 'a pullable and pushable' do
- let(:project) { current_project }
- end
-
- it_behaves_like 'container repository factory' do
- let(:project) { current_project }
- end
- end
-
- context 'allow to delete images since registry 2.7' do
- let(:current_params) do
- { scopes: ["repository:#{current_project.full_path}:delete"] }
- end
-
- it_behaves_like 'a deletable since registry 2.7' do
- let(:project) { current_project }
- end
- end
-
- context 'disallow to delete images' do
- let(:current_params) do
- { scopes: ["repository:#{current_project.full_path}:*"] }
- end
-
- it_behaves_like 'an inaccessible' do
- let(:project) { current_project }
- end
- end
-
- context 'for other projects' do
- context 'when pulling' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull"] }
- end
-
- context 'allow for public' do
- let_it_be(:project) { create(:project, :public) }
-
- it_behaves_like 'a pullable'
- it_behaves_like 'not a container repository factory'
- end
-
- shared_examples 'pullable for being team member' do
- context 'when you are not member' do
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'when you are member' do
- before_all do
- project.add_developer(current_user)
- end
-
- it_behaves_like 'a pullable'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'when you are owner' do
- let_it_be(:project) { create(:project, namespace: current_user.namespace) }
-
- it_behaves_like 'a pullable'
- it_behaves_like 'not a container repository factory'
- end
- end
-
- context 'for private' do
- let_it_be(:project) { create(:project, :private) }
-
- it_behaves_like 'pullable for being team member'
-
- context 'when you are admin' do
- let_it_be(:current_user) { create(:admin) }
-
- context 'when you are not member' do
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'when you are member' do
- before_all do
- project.add_developer(current_user)
- end
-
- it_behaves_like 'a pullable'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'when you are owner' do
- let_it_be(:project) { create(:project, namespace: current_user.namespace) }
-
- it_behaves_like 'a pullable'
- it_behaves_like 'not a container repository factory'
- end
- end
- end
- end
-
- context 'when pushing' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
- end
-
- context 'disallow for all' do
- context 'when you are member' do
- let_it_be(:project) { create(:project, :public) }
-
- before_all do
- project.add_developer(current_user)
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'when you are owner' do
- let_it_be(:project) { create(:project, :public, namespace: current_user.namespace) }
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
- end
- end
- end
-
- context 'for project without container registry' do
- let_it_be(:project) { create(:project, :public, container_registry_enabled: false) }
-
- before do
- project.update!(container_registry_enabled: false)
- end
-
- context 'disallow when pulling' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
- end
-
- context 'for project that disables repository' do
- let_it_be(:project) { create(:project, :public, :repository_disabled) }
-
- context 'disallow when pulling' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull"] }
- end
-
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
- end
- end
- end
-
- context 'registry catalog browsing authorized as admin' do
- let_it_be(:current_user) { create(:user, :admin) }
- let_it_be(:project) { create(:project, :public) }
-
- let(:current_params) do
- { scopes: ["registry:catalog:*"] }
- end
-
- it_behaves_like 'a browsable'
- end
-
- context 'support for multiple scopes' do
- let_it_be(:internal_project) { create(:project, :internal) }
- let_it_be(:private_project) { create(:project, :private) }
-
- let(:current_params) do
- {
- scopes: [
- "repository:#{internal_project.full_path}:pull",
- "repository:#{private_project.full_path}:pull"
- ]
- }
- end
-
- context 'user has access to all projects' do
- let_it_be(:current_user) { create(:user, :admin) }
-
- before do
- enable_admin_mode!(current_user)
- end
-
- it_behaves_like 'a browsable' do
- let(:access) do
- [
- { 'type' => 'repository',
- 'name' => internal_project.full_path,
- 'actions' => ['pull'] },
- { 'type' => 'repository',
- 'name' => private_project.full_path,
- 'actions' => ['pull'] }
- ]
- end
- end
- end
-
- context 'user only has access to internal project' do
- let_it_be(:current_user) { create(:user) }
-
- it_behaves_like 'a browsable' do
- let(:access) do
- [
- { 'type' => 'repository',
- 'name' => internal_project.full_path,
- 'actions' => ['pull'] }
- ]
- end
- end
- end
-
- context 'anonymous access is rejected' do
- let(:current_user) { nil }
-
- it_behaves_like 'a forbidden'
- end
- end
-
- context 'unauthorized' do
- context 'disallow to use scope-less authentication' do
- it_behaves_like 'a forbidden'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'for invalid scope' do
- let(:current_params) do
- { scopes: ['invalid:aa:bb'] }
- end
-
- it_behaves_like 'a forbidden'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'for private project' do
- let_it_be(:project) { create(:project, :private) }
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull"] }
- end
-
- it_behaves_like 'a forbidden'
- end
-
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
-
- context 'when pulling and pushing' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull,push"] }
- end
-
- it_behaves_like 'a pullable'
- it_behaves_like 'not a container repository factory'
- end
-
- context 'when pushing' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
- end
-
- it_behaves_like 'a forbidden'
- it_behaves_like 'not a container repository factory'
- end
- end
-
- context 'for registry catalog' do
- let(:current_params) do
- { scopes: ["registry:catalog:*"] }
- end
-
- it_behaves_like 'a forbidden'
- it_behaves_like 'not a container repository factory'
- end
- end
-
- context 'for deploy tokens' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull"] }
- end
-
- context 'when deploy token has read and write registry as scopes' do
- let(:current_user) { create(:deploy_token, write_registry: true, projects: [project]) }
-
- shared_examples 'able to login' do
- context 'registry provides read_container_image authentication_abilities' do
- let(:current_params) { {} }
- let(:authentication_abilities) { [:read_container_image] }
-
- it_behaves_like 'an authenticated'
- end
- end
-
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
-
- context 'when pulling' do
- it_behaves_like 'a pullable'
- end
-
- context 'when pushing' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
- end
-
- it_behaves_like 'a pushable'
- end
-
- it_behaves_like 'able to login'
- end
-
- context 'for internal project' do
- let_it_be(:project) { create(:project, :internal) }
-
- context 'when pulling' do
- it_behaves_like 'a pullable'
- end
-
- context 'when pushing' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
- end
-
- it_behaves_like 'a pushable'
- end
-
- it_behaves_like 'able to login'
- end
-
- context 'for private project' do
- let_it_be(:project) { create(:project, :private) }
-
- context 'when pulling' do
- it_behaves_like 'a pullable'
- end
-
- context 'when pushing' do
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
- end
-
- it_behaves_like 'a pushable'
- end
-
- it_behaves_like 'able to login'
- end
- end
-
- context 'when deploy token does not have read_registry scope' do
- let(:current_user) { create(:deploy_token, projects: [project], read_registry: false) }
-
- shared_examples 'unable to login' do
- context 'registry provides no container authentication_abilities' do
- let(:current_params) { {} }
- let(:authentication_abilities) { [] }
-
- it_behaves_like 'a forbidden'
- end
-
- context 'registry provides inapplicable container authentication_abilities' do
- let(:current_params) { {} }
- let(:authentication_abilities) { [:download_code] }
-
- it_behaves_like 'a forbidden'
- end
- end
-
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
-
- context 'when pulling' do
- it_behaves_like 'a pullable'
- end
-
- it_behaves_like 'unable to login'
- end
-
- context 'for internal project' do
- let_it_be(:project) { create(:project, :internal) }
-
- context 'when pulling' do
- it_behaves_like 'an inaccessible'
- end
-
- it_behaves_like 'unable to login'
- end
-
- context 'for private project' do
- let_it_be(:project) { create(:project, :internal) }
-
- context 'when pulling' do
- it_behaves_like 'an inaccessible'
- end
-
- context 'when logging in' do
- let(:current_params) { {} }
- let(:authentication_abilities) { [] }
-
- it_behaves_like 'a forbidden'
- end
-
- it_behaves_like 'unable to login'
- end
- end
-
- context 'when deploy token is not related to the project' do
- let_it_be(:current_user) { create(:deploy_token, read_registry: false) }
-
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
-
- context 'when pulling' do
- it_behaves_like 'a pullable'
- end
- end
-
- context 'for internal project' do
- let_it_be(:project) { create(:project, :internal) }
-
- context 'when pulling' do
- it_behaves_like 'an inaccessible'
- end
- end
-
- context 'for private project' do
- let_it_be(:project) { create(:project, :internal) }
-
- context 'when pulling' do
- it_behaves_like 'an inaccessible'
- end
- end
- end
-
- context 'when deploy token has been revoked' do
- let(:current_user) { create(:deploy_token, :revoked, projects: [project]) }
-
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
-
- it_behaves_like 'a pullable'
- end
-
- context 'for internal project' do
- let_it_be(:project) { create(:project, :internal) }
-
- it_behaves_like 'an inaccessible'
- end
-
- context 'for private project' do
- let_it_be(:project) { create(:project, :internal) }
-
- it_behaves_like 'an inaccessible'
- end
- end
- end
-
- context 'user authorization' do
- let_it_be(:current_user) { create(:user) }
-
- context 'with multiple scopes' do
- let_it_be(:project) { create(:project) }
-
- context 'allow developer to push images' do
- before_all do
- project.add_developer(current_user)
- end
-
- let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
- end
-
- it_behaves_like 'a pushable'
- it_behaves_like 'container repository factory'
- end
- end
- end
+ it_behaves_like 'a container registry auth service'
end
diff --git a/spec/services/auth/dependency_proxy_authentication_service_spec.rb b/spec/services/auth/dependency_proxy_authentication_service_spec.rb
new file mode 100644
index 00000000000..ba50149f53a
--- /dev/null
+++ b/spec/services/auth/dependency_proxy_authentication_service_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Auth::DependencyProxyAuthenticationService do
+ let_it_be(:user) { create(:user) }
+ let(:service) { Auth::DependencyProxyAuthenticationService.new(nil, user) }
+
+ before do
+ stub_config(dependency_proxy: { enabled: true })
+ end
+
+ describe '#execute' do
+ subject { service.execute(authentication_abilities: nil) }
+
+ context 'dependency proxy is not enabled' do
+ before do
+ stub_config(dependency_proxy: { enabled: false })
+ end
+
+ it 'returns not found' do
+ result = subject
+
+ expect(result[:http_status]).to eq(404)
+ expect(result[:message]).to eq('dependency proxy not enabled')
+ end
+ end
+
+ context 'without a user' do
+ let(:user) { nil }
+
+ it 'returns forbidden' do
+ result = subject
+
+ expect(result[:http_status]).to eq(403)
+ expect(result[:message]).to eq('access forbidden')
+ end
+ end
+
+ context 'with a user' do
+ it 'returns a token' do
+ expect(subject[:token]).not_to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
index 7b428550768..eaa5f723bec 100644
--- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
@@ -191,7 +191,7 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do
expect(mr_merge_if_green_enabled.merge_user).to be nil
end
- it 'Posts a system note' do
+ it 'posts a system note' do
note = mr_merge_if_green_enabled.notes.last
expect(note.note).to include 'canceled the automatic merge'
end
diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb
index 88b6c3098d1..d639fdbb46a 100644
--- a/spec/services/boards/lists/create_service_spec.rb
+++ b/spec/services/boards/lists/create_service_spec.rb
@@ -5,27 +5,29 @@ require 'spec_helper'
RSpec.describe Boards::Lists::CreateService do
describe '#execute' do
shared_examples 'creating board lists' do
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
- subject(:service) { described_class.new(parent, user, label_id: label.id) }
-
- before do
+ before_all do
parent.add_developer(user)
end
+ subject(:service) { described_class.new(parent, user, label_id: label.id) }
+
context 'when board lists is empty' do
it 'creates a new list at beginning of the list' do
- list = service.execute(board)
+ response = service.execute(board)
- expect(list.position).to eq 0
+ expect(response.success?).to eq(true)
+ expect(response.payload[:list].position).to eq 0
end
end
context 'when board lists has the done list' do
it 'creates a new list at beginning of the list' do
- list = service.execute(board)
+ response = service.execute(board)
- expect(list.position).to eq 0
+ expect(response.success?).to eq(true)
+ expect(response.payload[:list].position).to eq 0
end
end
@@ -34,9 +36,10 @@ RSpec.describe Boards::Lists::CreateService do
create(:list, board: board, position: 0)
create(:list, board: board, position: 1)
- list = service.execute(board)
+ response = service.execute(board)
- expect(list.position).to eq 2
+ expect(response.success?).to eq(true)
+ expect(response.payload[:list].position).to eq 2
end
end
@@ -44,32 +47,35 @@ RSpec.describe Boards::Lists::CreateService do
it 'creates a new list at end of the label lists' do
list1 = create(:list, board: board, position: 0)
- list2 = service.execute(board)
+ list2 = service.execute(board).payload[:list]
expect(list1.reload.position).to eq 0
expect(list2.reload.position).to eq 1
end
end
- context 'when provided label does not belongs to the parent' do
- it 'raises an error' do
+ context 'when provided label does not belong to the parent' do
+ it 'returns an error' do
label = create(:label, name: 'in-development')
service = described_class.new(parent, user, label_id: label.id)
- expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound)
+ response = service.execute(board)
+
+ expect(response.success?).to eq(false)
+ expect(response.errors).to include('Label not found')
end
end
context 'when backlog param is sent' do
it 'creates one and only one backlog list' do
service = described_class.new(parent, user, 'backlog' => true)
- list = service.execute(board)
+ list = service.execute(board).payload[:list]
expect(list.list_type).to eq('backlog')
expect(list.position).to be_nil
expect(list).to be_valid
- another_backlog = service.execute(board)
+ another_backlog = service.execute(board).payload[:list]
expect(another_backlog).to eq list
end
@@ -77,17 +83,17 @@ RSpec.describe Boards::Lists::CreateService do
end
context 'when board parent is a project' do
- let(:parent) { create(:project) }
- let(:board) { create(:board, project: parent) }
- let(:label) { create(:label, project: parent, name: 'in-progress') }
+ let_it_be(:parent) { create(:project) }
+ let_it_be(:board) { create(:board, project: parent) }
+ let_it_be(:label) { create(:label, project: parent, name: 'in-progress') }
it_behaves_like 'creating board lists'
end
context 'when board parent is a group' do
- let(:parent) { create(:group) }
- let(:board) { create(:board, group: parent) }
- let(:label) { create(:group_label, group: parent, name: 'in-progress') }
+ let_it_be(:parent) { create(:group) }
+ let_it_be(:board) { create(:board, group: parent) }
+ let_it_be(:label) { create(:group_label, group: parent, name: 'in-progress') }
it_behaves_like 'creating board lists'
end
diff --git a/spec/services/ci/compare_accessibility_reports_service_spec.rb b/spec/services/ci/compare_accessibility_reports_service_spec.rb
index 6903a633eeb..e0b84219834 100644
--- a/spec/services/ci/compare_accessibility_reports_service_spec.rb
+++ b/spec/services/ci/compare_accessibility_reports_service_spec.rb
@@ -29,34 +29,4 @@ RSpec.describe Ci::CompareAccessibilityReportsService do
end
end
end
-
- describe '#latest?' do
- subject { service.latest?(base_pipeline, head_pipeline, data) }
-
- let!(:base_pipeline) { nil }
- let!(:head_pipeline) { create(:ci_pipeline, :with_accessibility_reports, project: project) }
- let!(:key) { service.send(:key, base_pipeline, head_pipeline) }
-
- context 'when cache key is latest' do
- let(:data) { { key: key } }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when cache key is outdated' do
- before do
- head_pipeline.update_column(:updated_at, 10.minutes.ago)
- end
-
- let(:data) { { key: key } }
-
- it { is_expected.to be_falsy }
- end
-
- context 'when cache key is empty' do
- let(:data) { { key: nil } }
-
- it { is_expected.to be_falsy }
- end
- end
end
diff --git a/spec/services/ci/compare_codequality_reports_service_spec.rb b/spec/services/ci/compare_codequality_reports_service_spec.rb
new file mode 100644
index 00000000000..ef762a2e9ad
--- /dev/null
+++ b/spec/services/ci/compare_codequality_reports_service_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::CompareCodequalityReportsService do
+ let(:service) { described_class.new(project) }
+ let(:project) { create(:project, :repository) }
+
+ describe '#execute' do
+ subject { service.execute(base_pipeline, head_pipeline) }
+
+ context 'when head pipeline has a codequality report' do
+ let(:base_pipeline) { nil }
+ let(:head_pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }
+
+ it 'returns status and data' do
+ expect(subject[:status]).to eq(:parsed)
+ expect(subject[:data]).to match_schema('entities/codequality_reports_comparer')
+ end
+ end
+
+ context 'when base and head pipelines have codequality reports' do
+ let(:base_pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }
+ let(:head_pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }
+
+ it 'returns status and data' do
+ expect(subject[:status]).to eq(:parsed)
+ expect(subject[:data]).to match_schema('entities/codequality_reports_comparer')
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/compare_reports_base_service_spec.rb b/spec/services/ci/compare_reports_base_service_spec.rb
new file mode 100644
index 00000000000..9ce58c4972d
--- /dev/null
+++ b/spec/services/ci/compare_reports_base_service_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::CompareReportsBaseService do
+ let(:service) { described_class.new(project) }
+ let(:project) { create(:project, :repository) }
+
+ describe '#latest?' do
+ subject { service.latest?(base_pipeline, head_pipeline, data) }
+
+ let!(:base_pipeline) { nil }
+ let!(:head_pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
+ let!(:key) { service.send(:key, base_pipeline, head_pipeline) }
+
+ context 'when cache key is latest' do
+ let(:data) { { key: key } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when cache key is outdated' do
+ before do
+ head_pipeline.update_column(:updated_at, 10.minutes.ago)
+ end
+
+ let(:data) { { key: key } }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when cache key is empty' do
+ let(:data) { { key: nil } }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+end
diff --git a/spec/services/ci/compare_test_reports_service_spec.rb b/spec/services/ci/compare_test_reports_service_spec.rb
index 377c801b008..01d58b2095f 100644
--- a/spec/services/ci/compare_test_reports_service_spec.rb
+++ b/spec/services/ci/compare_test_reports_service_spec.rb
@@ -67,7 +67,7 @@ RSpec.describe Ci::CompareTestReportsService do
# The JUnit fixture for the given build has 3 failures.
# This service will create 1 test case failure record for each.
- Ci::TestCasesService.new.execute(build)
+ Ci::TestFailureHistoryService.new(head_pipeline).execute
end
it 'loads recent failures on limited test cases to avoid building up a huge DB query', :aggregate_failures do
@@ -80,34 +80,4 @@ RSpec.describe Ci::CompareTestReportsService do
end
end
end
-
- describe '#latest?' do
- subject { service.latest?(base_pipeline, head_pipeline, data) }
-
- let!(:base_pipeline) { nil }
- let!(:head_pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
- let!(:key) { service.send(:key, base_pipeline, head_pipeline) }
-
- context 'when cache key is latest' do
- let(:data) { { key: key } }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when cache key is outdated' do
- before do
- head_pipeline.update_column(:updated_at, 10.minutes.ago)
- end
-
- let(:data) { { key: key } }
-
- it { is_expected.to be_falsy }
- end
-
- context 'when cache key is empty' do
- let(:data) { { key: nil } }
-
- it { is_expected.to be_falsy }
- end
- end
end
diff --git a/spec/services/ci/create_job_artifacts_service_spec.rb b/spec/services/ci/create_job_artifacts_service_spec.rb
index 72b0d220b11..29e51a23dea 100644
--- a/spec/services/ci/create_job_artifacts_service_spec.rb
+++ b/spec/services/ci/create_job_artifacts_service_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe Ci::CreateJobArtifactsService do
upload = Tempfile.new('upload')
FileUtils.copy(path, upload.path)
- UploadedFile.new(upload.path, params)
+ UploadedFile.new(upload.path, **params)
end
describe '#execute' do
diff --git a/spec/services/ci/create_pipeline_service/merge_requests_spec.rb b/spec/services/ci/create_pipeline_service/merge_requests_spec.rb
new file mode 100644
index 00000000000..e5347faed6a
--- /dev/null
+++ b/spec/services/ci/create_pipeline_service/merge_requests_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::CreatePipelineService do
+ context 'merge requests handling' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { project.owner }
+
+ let(:ref) { 'refs/heads/feature' }
+ let(:source) { :push }
+ let(:service) { described_class.new(project, user, { ref: ref }) }
+ let(:pipeline) { service.execute(source) }
+
+ before do
+ stub_ci_pipeline_yaml_file <<-EOS
+ workflow:
+ rules:
+ # do not create pipelines if merge requests are opened
+ - if: $CI_OPEN_MERGE_REQUESTS
+ when: never
+
+ - if: $CI_COMMIT_BRANCH
+
+ rspec:
+ script: echo Hello World
+ EOS
+ end
+
+ context 'when pushing a change' do
+ context 'when a merge request already exists' do
+ let!(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'does not create a pipeline' do
+ expect(pipeline).not_to be_persisted
+ end
+ end
+
+ context 'when no merge request exists' do
+ it 'does create a pipeline' do
+ expect(pipeline.errors).to be_empty
+ expect(pipeline).to be_persisted
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service/rules_spec.rb b/spec/services/ci/create_pipeline_service/rules_spec.rb
index a0ff2fff0ef..ac6c4c188e4 100644
--- a/spec/services/ci/create_pipeline_service/rules_spec.rb
+++ b/spec/services/ci/create_pipeline_service/rules_spec.rb
@@ -93,6 +93,148 @@ RSpec.describe Ci::CreatePipelineService do
end
end
end
+
+ context 'with allow_failure and exit_codes', :aggregate_failures do
+ def find_job(name)
+ pipeline.builds.find_by(name: name)
+ end
+
+ let(:config) do
+ <<-EOY
+ job-1:
+ script: exit 42
+ allow_failure:
+ exit_codes: 42
+ rules:
+ - if: $CI_COMMIT_REF_NAME == "master"
+ allow_failure: false
+
+ job-2:
+ script: exit 42
+ allow_failure:
+ exit_codes: 42
+ rules:
+ - if: $CI_COMMIT_REF_NAME == "master"
+ allow_failure: true
+
+ job-3:
+ script: exit 42
+ allow_failure:
+ exit_codes: 42
+ rules:
+ - if: $CI_COMMIT_REF_NAME == "master"
+ when: manual
+ EOY
+ end
+
+ it 'creates a pipeline' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly(
+ 'job-1', 'job-2', 'job-3'
+ )
+ end
+
+ it 'assigns job:allow_failure values to the builds' do
+ expect(find_job('job-1').allow_failure).to eq(false)
+ expect(find_job('job-2').allow_failure).to eq(true)
+ expect(find_job('job-3').allow_failure).to eq(false)
+ end
+
+ it 'removes exit_codes if allow_failure is specified' do
+ expect(find_job('job-1').options.dig(:allow_failure_criteria)).to be_nil
+ expect(find_job('job-2').options.dig(:allow_failure_criteria)).to be_nil
+ expect(find_job('job-3').options.dig(:allow_failure_criteria, :exit_codes)).to eq([42])
+ end
+
+ context 'with ci_allow_failure_with_exit_codes disabled' do
+ before do
+ stub_feature_flags(ci_allow_failure_with_exit_codes: false)
+ end
+
+ it 'does not persist allow_failure_criteria' do
+ expect(pipeline).to be_persisted
+
+ expect(find_job('job-1').options.key?(:allow_failure_criteria)).to be_falsey
+ expect(find_job('job-2').options.key?(:allow_failure_criteria)).to be_falsey
+ expect(find_job('job-3').options.key?(:allow_failure_criteria)).to be_falsey
+ end
+ end
+ end
+
+ context 'if:' do
+ context 'variables:' do
+ let(:config) do
+ <<-EOY
+ job:
+ script: "echo job1"
+ variables:
+ VAR1: my var 1
+ VAR2: my var 2
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ variables:
+ VAR1: overridden var 1
+ - if: $CI_COMMIT_REF_NAME =~ /feature/
+ variables:
+ VAR2: overridden var 2
+ VAR3: new var 3
+ - when: on_success
+ EOY
+ end
+
+ let(:job) { pipeline.builds.find_by(name: 'job') }
+
+ context 'when matching to the first rule' do
+ let(:ref) { 'refs/heads/master' }
+
+ it 'overrides VAR1' do
+ variables = job.scoped_variables_hash
+
+ expect(variables['VAR1']).to eq('overridden var 1')
+ expect(variables['VAR2']).to eq('my var 2')
+ expect(variables['VAR3']).to be_nil
+ end
+
+ context 'when FF ci_rules_variables is disabled' do
+ before do
+ stub_feature_flags(ci_rules_variables: false)
+ end
+
+ it 'does not affect variables' do
+ variables = job.scoped_variables_hash
+
+ expect(variables['VAR1']).to eq('my var 1')
+ expect(variables['VAR2']).to eq('my var 2')
+ expect(variables['VAR3']).to be_nil
+ end
+ end
+ end
+
+ context 'when matching to the second rule' do
+ let(:ref) { 'refs/heads/feature' }
+
+ it 'overrides VAR2 and adds VAR3' do
+ variables = job.scoped_variables_hash
+
+ expect(variables['VAR1']).to eq('my var 1')
+ expect(variables['VAR2']).to eq('overridden var 2')
+ expect(variables['VAR3']).to eq('new var 3')
+ end
+ end
+
+ context 'when no match' do
+ let(:ref) { 'refs/heads/wip' }
+
+ it 'does not affect vars' do
+ variables = job.scoped_variables_hash
+
+ expect(variables['VAR1']).to eq('my var 1')
+ expect(variables['VAR2']).to eq('my var 2')
+ expect(variables['VAR3']).to be_nil
+ end
+ end
+ end
+ end
end
context 'when workflow:rules are used' do
diff --git a/spec/services/ci/generate_coverage_reports_service_spec.rb b/spec/services/ci/generate_coverage_reports_service_spec.rb
index d39053adebc..d12a9268e7e 100644
--- a/spec/services/ci/generate_coverage_reports_service_spec.rb
+++ b/spec/services/ci/generate_coverage_reports_service_spec.rb
@@ -52,34 +52,4 @@ RSpec.describe Ci::GenerateCoverageReportsService do
end
end
end
-
- describe '#latest?' do
- subject { service.latest?(base_pipeline, head_pipeline, data) }
-
- let!(:base_pipeline) { nil }
- let!(:head_pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
- let!(:key) { service.send(:key, base_pipeline, head_pipeline) }
-
- context 'when cache key is latest' do
- let(:data) { { key: key } }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when cache key is outdated' do
- before do
- head_pipeline.update_column(:updated_at, 10.minutes.ago)
- end
-
- let(:data) { { key: key } }
-
- it { is_expected.to be_falsy }
- end
-
- context 'when cache key is empty' do
- let(:data) { { key: nil } }
-
- it { is_expected.to be_falsy }
- end
- end
end
diff --git a/spec/services/ci/generate_terraform_reports_service_spec.rb b/spec/services/ci/generate_terraform_reports_service_spec.rb
index 25bf96035b2..c9ac74e050c 100644
--- a/spec/services/ci/generate_terraform_reports_service_spec.rb
+++ b/spec/services/ci/generate_terraform_reports_service_spec.rb
@@ -64,33 +64,4 @@ RSpec.describe Ci::GenerateTerraformReportsService do
end
end
end
-
- describe '#latest?' do
- let_it_be(:head_pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
-
- subject { described_class.new(project) }
-
- it 'returns true when cache key is latest' do
- cache_key = subject.send(:key, nil, head_pipeline)
-
- result = subject.latest?(nil, head_pipeline, key: cache_key)
-
- expect(result).to eq(true)
- end
-
- it 'returns false when cache key is outdated' do
- cache_key = subject.send(:key, nil, head_pipeline)
- head_pipeline.update_column(:updated_at, 10.minutes.ago)
-
- result = subject.latest?(nil, head_pipeline, key: cache_key)
-
- expect(result).to eq(false)
- end
-
- it 'returns false when cache key is nil' do
- result = subject.latest?(nil, head_pipeline, key: nil)
-
- expect(result).to eq(false)
- end
- end
end
diff --git a/spec/services/ci/list_config_variables_service_spec.rb b/spec/services/ci/list_config_variables_service_spec.rb
index 95c98c2b5ef..1735f4cfc97 100644
--- a/spec/services/ci/list_config_variables_service_spec.rb
+++ b/spec/services/ci/list_config_variables_service_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-RSpec.describe Ci::ListConfigVariablesService do
+RSpec.describe Ci::ListConfigVariablesService, :use_clean_rails_memory_store_caching do
+ include ReactiveCachingHelpers
+
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
let(:service) { described_class.new(project, user) }
@@ -31,6 +33,10 @@ RSpec.describe Ci::ListConfigVariablesService do
}
end
+ before do
+ synchronous_reactive_cache(service)
+ end
+
it 'returns variable list' do
expect(subject['KEY1']).to eq({ value: 'val 1', description: 'description 1' })
expect(subject['KEY2']).to eq({ value: 'val 2', description: '' })
@@ -65,6 +71,8 @@ RSpec.describe Ci::ListConfigVariablesService do
HEREDOC
end
end
+
+ synchronous_reactive_cache(service)
end
it 'returns variable list' do
@@ -77,6 +85,10 @@ RSpec.describe Ci::ListConfigVariablesService do
let(:sha) { 'invalid-sha' }
let(:ci_config) { nil }
+ before do
+ synchronous_reactive_cache(service)
+ end
+
it 'returns empty json' do
expect(subject).to eq({})
end
@@ -96,11 +108,44 @@ RSpec.describe Ci::ListConfigVariablesService do
}
end
+ before do
+ synchronous_reactive_cache(service)
+ end
+
it 'returns empty result' do
expect(subject).to eq({})
end
end
+ context 'when reading from cache' do
+ let(:sha) { 'master' }
+ let(:ci_config) { {} }
+ let(:reactive_cache_params) { [sha] }
+ let(:return_value) { { 'KEY1' => { value: 'val 1', description: 'description 1' } } }
+
+ before do
+ stub_reactive_cache(service, return_value, reactive_cache_params)
+ end
+
+ it 'returns variable list' do
+ expect(subject).to eq(return_value)
+ end
+ end
+
+ context 'when the cache is empty' do
+ let(:sha) { 'master' }
+ let(:ci_config) { {} }
+ let(:reactive_cache_params) { [sha] }
+
+ it 'returns nil and enquques the worker to fill cache' do
+ expect(ExternalServiceReactiveCachingWorker)
+ .to receive(:perform_async)
+ .with(service.class, service.id, *reactive_cache_params)
+
+ expect(subject).to be_nil
+ end
+ end
+
private
def stub_gitlab_ci_yml_for_sha(sha, result)
diff --git a/spec/services/ci/pipelines/create_artifact_service_spec.rb b/spec/services/ci/pipelines/create_artifact_service_spec.rb
index 6f177889ed3..4e9248d9d1a 100644
--- a/spec/services/ci/pipelines/create_artifact_service_spec.rb
+++ b/spec/services/ci/pipelines/create_artifact_service_spec.rb
@@ -7,7 +7,8 @@ RSpec.describe ::Ci::Pipelines::CreateArtifactService do
subject { described_class.new.execute(pipeline) }
context 'when pipeline has coverage reports' do
- let(:pipeline) { create(:ci_pipeline, :with_coverage_reports) }
+ let(:project) { create(:project, :repository) }
+ let(:pipeline) { create(:ci_pipeline, :with_coverage_reports, project: project) }
context 'when pipeline is finished' do
it 'creates a pipeline artifact' do
diff --git a/spec/services/ci/test_cases_service_spec.rb b/spec/services/ci/test_cases_service_spec.rb
deleted file mode 100644
index b61d308640f..00000000000
--- a/spec/services/ci/test_cases_service_spec.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Ci::TestCasesService, :aggregate_failures do
- describe '#execute' do
- subject(:execute_service) { described_class.new.execute(build) }
-
- context 'when build has test reports' do
- let(:build) { create(:ci_build, :success, :test_reports) } # The test report has 2 test case failures
-
- it 'creates test case failures records' do
- execute_service
-
- expect(Ci::TestCase.count).to eq(2)
- expect(Ci::TestCaseFailure.count).to eq(2)
- end
-
- context 'when feature flag for test failure history is disabled' do
- before do
- stub_feature_flags(test_failure_history: false)
- end
-
- it 'does not persist data' do
- execute_service
-
- expect(Ci::TestCase.count).to eq(0)
- expect(Ci::TestCaseFailure.count).to eq(0)
- end
- end
-
- context 'when build is not for the default branch' do
- before do
- build.update_column(:ref, 'new-feature')
- end
-
- it 'does not persist data' do
- execute_service
-
- expect(Ci::TestCase.count).to eq(0)
- expect(Ci::TestCaseFailure.count).to eq(0)
- end
- end
-
- context 'when test failure data have already been persisted with the same exact attributes' do
- before do
- execute_service
- end
-
- it 'does not fail but does not persist new data' do
- expect { described_class.new.execute(build) }.not_to raise_error
-
- expect(Ci::TestCase.count).to eq(2)
- expect(Ci::TestCaseFailure.count).to eq(2)
- end
- end
-
- context 'when test failure data have duplicates within the same payload (happens when the JUnit report has duplicate test case names but have different failures)' do
- let(:build) { create(:ci_build, :success, :test_reports_with_duplicate_failed_test_names) } # The test report has 2 test case failures but with the same test case keys
-
- it 'does not fail but does not persist duplicate data' do
- expect { described_class.new.execute(build) }.not_to raise_error
-
- expect(Ci::TestCase.count).to eq(1)
- expect(Ci::TestCaseFailure.count).to eq(1)
- end
- end
-
- context 'when number of failed test cases exceed the limit' do
- before do
- stub_const("#{described_class.name}::MAX_TRACKABLE_FAILURES", 1)
- end
-
- it 'does not persist data' do
- execute_service
-
- expect(Ci::TestCase.count).to eq(0)
- expect(Ci::TestCaseFailure.count).to eq(0)
- end
- end
- end
-
- context 'when build has no test reports' do
- let(:build) { create(:ci_build, :running) }
-
- it 'does not persist data' do
- execute_service
-
- expect(Ci::TestCase.count).to eq(0)
- expect(Ci::TestCaseFailure.count).to eq(0)
- end
- end
- end
-end
diff --git a/spec/services/ci/test_failure_history_service_spec.rb b/spec/services/ci/test_failure_history_service_spec.rb
new file mode 100644
index 00000000000..e858c85490d
--- /dev/null
+++ b/spec/services/ci/test_failure_history_service_spec.rb
@@ -0,0 +1,192 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
+ describe '#execute' do
+ let(:project) { create(:project) }
+ let(:pipeline) { create(:ci_empty_pipeline, status: :created, project: project) }
+
+ subject(:execute_service) { described_class.new(pipeline).execute }
+
+ context 'when pipeline has failed builds with test reports' do
+ before do
+ # The test report has 2 test case failures
+ create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
+ end
+
+ it 'creates test case failures records' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(2)
+ expect(Ci::TestCaseFailure.count).to eq(2)
+ end
+
+ context 'when feature flag for test failure history is disabled' do
+ before do
+ stub_feature_flags(test_failure_history: false)
+ end
+
+ it 'does not persist data' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(0)
+ expect(Ci::TestCaseFailure.count).to eq(0)
+ end
+ end
+
+ context 'when pipeline is not for the default branch' do
+ before do
+ pipeline.update_column(:ref, 'new-feature')
+ end
+
+ it 'does not persist data' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(0)
+ expect(Ci::TestCaseFailure.count).to eq(0)
+ end
+ end
+
+ context 'when test failure data have already been persisted with the same exact attributes' do
+ before do
+ execute_service
+ end
+
+ it 'does not fail but does not persist new data' do
+ expect { described_class.new(pipeline).execute }.not_to raise_error
+
+ expect(Ci::TestCase.count).to eq(2)
+ expect(Ci::TestCaseFailure.count).to eq(2)
+ end
+ end
+
+ context 'when number of failed test cases exceed the limit' do
+ before do
+ stub_const("#{described_class.name}::MAX_TRACKABLE_FAILURES", 1)
+ end
+
+ it 'does not persist data' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(0)
+ expect(Ci::TestCaseFailure.count).to eq(0)
+ end
+ end
+
+ context 'when number of failed test cases across multiple builds exceed the limit' do
+ before do
+ stub_const("#{described_class.name}::MAX_TRACKABLE_FAILURES", 2)
+
+ # This other test report has 1 unique test case failure which brings us to 3 total failures across all builds
+ # thus exceeding the limit of 2 for MAX_TRACKABLE_FAILURES
+ create(:ci_build, :failed, :test_reports_with_duplicate_failed_test_names, pipeline: pipeline, project: project)
+ end
+
+ it 'does not persist data' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(0)
+ expect(Ci::TestCaseFailure.count).to eq(0)
+ end
+ end
+ end
+
+ context 'when test failure data have duplicates within the same payload (happens when the JUnit report has duplicate test case names but have different failures)' do
+ before do
+ # The test report has 2 test case failures but with the same test case keys
+ create(:ci_build, :failed, :test_reports_with_duplicate_failed_test_names, pipeline: pipeline, project: project)
+ end
+
+ it 'does not fail but does not persist duplicate data' do
+ expect { execute_service }.not_to raise_error
+
+ expect(Ci::TestCase.count).to eq(1)
+ expect(Ci::TestCaseFailure.count).to eq(1)
+ end
+ end
+
+ context 'when pipeline has no failed builds with test reports' do
+ before do
+ create(:ci_build, :test_reports, pipeline: pipeline, project: project)
+ create(:ci_build, :failed, pipeline: pipeline, project: project)
+ end
+
+ it 'does not persist data' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(0)
+ expect(Ci::TestCaseFailure.count).to eq(0)
+ end
+ end
+ end
+
+ describe '#should_track_failures?' do
+ let(:project) { create(:project, :repository) }
+ let(:pipeline) { create(:ci_empty_pipeline, status: :created, project: project, ref: project.default_branch) }
+
+ subject { described_class.new(pipeline).should_track_failures? }
+
+ before do
+ create(:ci_build, :test_reports, :failed, pipeline: pipeline, project: project)
+ create(:ci_build, :test_reports, :failed, pipeline: pipeline, project: project)
+ end
+
+ context 'when feature flag is enabled and pipeline ref is the default branch' do
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(test_failure_history: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when pipeline is not equal to the project default branch' do
+ before do
+ pipeline.update_column(:ref, 'some-other-branch')
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when total number of builds with failed tests exceeds the max number of trackable failures' do
+ before do
+ stub_const("#{described_class.name}::MAX_TRACKABLE_FAILURES", 1)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '#async' do
+ let(:pipeline) { double(id: 1) }
+ let(:service) { described_class.new(pipeline) }
+
+ context 'when service should track failures' do
+ before do
+ allow(service).to receive(:should_track_failures?).and_return(true)
+ end
+
+ it 'enqueues the worker when #perform_if_needed is called' do
+ expect(Ci::TestFailureHistoryWorker).to receive(:perform_async).with(pipeline.id)
+
+ service.async.perform_if_needed
+ end
+ end
+
+ context 'when service should not track failures' do
+ before do
+ allow(service).to receive(:should_track_failures?).and_return(false)
+ end
+
+ it 'does not enqueue the worker when #perform_if_needed is called' do
+ expect(Ci::TestFailureHistoryWorker).not_to receive(:perform_async)
+
+ service.async.perform_if_needed
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/update_build_state_service_spec.rb b/spec/services/ci/update_build_state_service_spec.rb
index 2545909bf56..3112e5dda1b 100644
--- a/spec/services/ci/update_build_state_service_spec.rb
+++ b/spec/services/ci/update_build_state_service_spec.rb
@@ -80,7 +80,11 @@ RSpec.describe Ci::UpdateBuildStateService do
context 'when build has a checksum' do
let(:params) do
- { checksum: 'crc32:12345678', state: 'failed', failure_reason: 'script_failure' }
+ {
+ output: { checksum: 'crc32:12345678', bytesize: 123 },
+ failure_reason: 'script_failure',
+ state: 'failed'
+ }
end
context 'when build does not have associated trace chunks' do
@@ -154,14 +158,74 @@ RSpec.describe Ci::UpdateBuildStateService do
end
context 'when trace checksum is valid' do
- let(:params) { { checksum: 'crc32:ed82cd11', state: 'success' } }
+ let(:params) do
+ { output: { checksum: 'crc32:ed82cd11', bytesize: 4 }, state: 'success' }
+ end
- it 'does not increment invalid trace metric' do
+ it 'does not increment invalid or corrupted trace metric' do
execute_with_stubbed_metrics!
expect(metrics)
.not_to have_received(:increment_trace_operation)
.with(operation: :invalid)
+
+ expect(metrics)
+ .not_to have_received(:increment_trace_operation)
+ .with(operation: :corrupted)
+ end
+
+ context 'when using deprecated parameters' do
+ let(:params) do
+ { checksum: 'crc32:ed82cd11', state: 'success' }
+ end
+
+ it 'does not increment invalid or corrupted trace metric' do
+ execute_with_stubbed_metrics!
+
+ expect(metrics)
+ .not_to have_received(:increment_trace_operation)
+ .with(operation: :invalid)
+
+ expect(metrics)
+ .not_to have_received(:increment_trace_operation)
+ .with(operation: :corrupted)
+ end
+ end
+ end
+
+ context 'when trace checksum is invalid and the log is corrupted' do
+ let(:params) do
+ { output: { checksum: 'crc32:12345678', bytesize: 1 }, state: 'success' }
+ end
+
+ it 'increments invalid and corrupted trace metrics' do
+ execute_with_stubbed_metrics!
+
+ expect(metrics)
+ .to have_received(:increment_trace_operation)
+ .with(operation: :invalid)
+
+ expect(metrics)
+ .to have_received(:increment_trace_operation)
+ .with(operation: :corrupted)
+ end
+ end
+
+ context 'when trace checksum is invalid but the log seems fine' do
+ let(:params) do
+ { output: { checksum: 'crc32:12345678', bytesize: 4 }, state: 'success' }
+ end
+
+ it 'does not increment corrupted trace metric' do
+ execute_with_stubbed_metrics!
+
+ expect(metrics)
+ .to have_received(:increment_trace_operation)
+ .with(operation: :invalid)
+
+ expect(metrics)
+ .not_to have_received(:increment_trace_operation)
+ .with(operation: :corrupted)
end
end
diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb
index f93ae2c62f3..f3b420510a6 100644
--- a/spec/services/clusters/applications/create_service_spec.rb
+++ b/spec/services/clusters/applications/create_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Clusters::Applications::CreateService do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:user) { create(:user) }
- let(:params) { { application: 'helm' } }
+ let(:params) { { application: 'ingress' } }
let(:service) { described_class.new(cluster, user, params) }
describe '#execute' do
@@ -23,16 +23,16 @@ RSpec.describe Clusters::Applications::CreateService do
subject
cluster.reload
- end.to change(cluster, :application_helm)
+ end.to change(cluster, :application_ingress)
end
context 'application already installed' do
- let!(:application) { create(:clusters_applications_helm, :installed, cluster: cluster) }
+ let!(:application) { create(:clusters_applications_ingress, :installed, cluster: cluster) }
it 'does not create a new application' do
expect do
subject
- end.not_to change(Clusters::Applications::Helm, :count)
+ end.not_to change(Clusters::Applications::Ingress, :count)
end
it 'schedules an upgrade for the application' do
@@ -43,10 +43,6 @@ RSpec.describe Clusters::Applications::CreateService do
end
context 'known applications' do
- before do
- create(:clusters_applications_helm, :installed, cluster: cluster)
- end
-
context 'ingress application' do
let(:params) do
{
@@ -215,19 +211,17 @@ RSpec.describe Clusters::Applications::CreateService do
using RSpec::Parameterized::TableSyntax
- where(:application, :association, :allowed, :pre_create_helm, :pre_create_ingress) do
- 'helm' | :application_helm | true | false | false
- 'ingress' | :application_ingress | true | true | false
- 'runner' | :application_runner | true | true | false
- 'prometheus' | :application_prometheus | true | true | false
- 'jupyter' | :application_jupyter | true | true | true
+ where(:application, :association, :allowed, :pre_create_ingress) do
+ 'ingress' | :application_ingress | true | false
+ 'runner' | :application_runner | true | false
+ 'prometheus' | :application_prometheus | true | false
+ 'jupyter' | :application_jupyter | true | true
end
with_them do
before do
klass = "Clusters::Applications::#{application.titleize}"
allow_any_instance_of(klass.constantize).to receive(:make_scheduled!).and_call_original
- create(:clusters_applications_helm, :installed, cluster: cluster) if pre_create_helm
create(:clusters_applications_ingress, :installed, cluster: cluster, external_hostname: 'example.com') if pre_create_ingress
end
@@ -252,7 +246,7 @@ RSpec.describe Clusters::Applications::CreateService do
it 'makes the application scheduled' do
expect do
subject
- end.to change { Clusters::Applications::Helm.with_status(:scheduled).count }.by(1)
+ end.to change { Clusters::Applications::Ingress.with_status(:scheduled).count }.by(1)
end
it 'schedules an install via worker' do
@@ -266,7 +260,7 @@ RSpec.describe Clusters::Applications::CreateService do
end
context 'when application is associated with a cluster' do
- let(:application) { create(:clusters_applications_helm, :installable, cluster: cluster) }
+ let(:application) { create(:clusters_applications_ingress, :installable, cluster: cluster) }
let(:worker_arguments) { [application.name, application.id] }
it_behaves_like 'installable applications'
@@ -280,7 +274,7 @@ RSpec.describe Clusters::Applications::CreateService do
end
context 'when installation is already in progress' do
- let!(:application) { create(:clusters_applications_helm, :installing, cluster: cluster) }
+ let!(:application) { create(:clusters_applications_ingress, :installing, cluster: cluster) }
it 'raises an exception' do
expect { subject }
@@ -295,7 +289,7 @@ RSpec.describe Clusters::Applications::CreateService do
context 'when application is installed' do
%i(installed updated).each do |status|
- let(:application) { create(:clusters_applications_helm, status, cluster: cluster) }
+ let(:application) { create(:clusters_applications_ingress, status, cluster: cluster) }
it 'schedules an upgrade via worker' do
expect(ClusterUpgradeAppWorker)
diff --git a/spec/services/clusters/applications/prometheus_health_check_service_spec.rb b/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
index fc5a80688e6..ee47d00f700 100644
--- a/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
+++ b/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Clusters::Applications::PrometheusHealthCheckService, '#execute'
RSpec.shared_examples 'sends alert' do
it 'sends an alert' do
expect_next_instance_of(Projects::Alerting::NotifyService) do |notify_service|
- expect(notify_service).to receive(:execute).with(alerts_service.token)
+ expect(notify_service).to receive(:execute).with(integration.token, integration)
end
subject
@@ -40,8 +40,8 @@ RSpec.describe Clusters::Applications::PrometheusHealthCheckService, '#execute'
end
context 'when cluster is project_type' do
- let_it_be(:alerts_service) { create(:alerts_service) }
- let_it_be(:project) { create(:project, alerts_service: alerts_service) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
let(:applications_prometheus_healthy) { true }
let(:prometheus) { create(:clusters_applications_prometheus, status: prometheus_status_value, healthy: applications_prometheus_healthy) }
let(:cluster) { create(:cluster, :project, application_prometheus: prometheus, projects: [project]) }
diff --git a/spec/services/clusters/aws/authorize_role_service_spec.rb b/spec/services/clusters/aws/authorize_role_service_spec.rb
index 302bae6e3ff..17bbc372675 100644
--- a/spec/services/clusters/aws/authorize_role_service_spec.rb
+++ b/spec/services/clusters/aws/authorize_role_service_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Clusters::Aws::AuthorizeRoleService do
shared_examples 'bad request' do
it 'returns an empty hash' do
expect(subject.status).to eq(:unprocessable_entity)
- expect(subject.body).to eq({})
+ expect(subject.body).to eq({ message: message })
end
it 'logs the error' do
@@ -52,12 +52,14 @@ RSpec.describe Clusters::Aws::AuthorizeRoleService do
context 'role does not exist' do
let(:user) { create(:user) }
+ let(:message) { 'Error: Unable to find AWS role for current user' }
include_examples 'bad request'
end
context 'supplied ARN is invalid' do
let(:role_arn) { 'invalid' }
+ let(:message) { 'Validation failed: Role arn must be a valid Amazon Resource Name' }
include_examples 'bad request'
end
@@ -69,18 +71,29 @@ RSpec.describe Clusters::Aws::AuthorizeRoleService do
context 'error fetching credentials' do
let(:error) { Aws::STS::Errors::ServiceError.new(nil, 'error message') }
+ let(:message) { 'AWS service error: error message' }
+
+ include_examples 'bad request'
+ end
+
+ context 'error in assuming role' do
+ let(:raw_message) { "User foo is not authorized to perform: sts:AssumeRole on resource bar" }
+ let(:error) { Aws::STS::Errors::AccessDenied.new(nil, raw_message) }
+ let(:message) { "Access denied: #{raw_message}" }
include_examples 'bad request'
end
context 'credentials not configured' do
let(:error) { Aws::Errors::MissingCredentialsError.new('error message') }
+ let(:message) { "Error: No AWS credentials were supplied" }
include_examples 'bad request'
end
context 'role not configured' do
let(:error) { Clusters::Aws::FetchCredentialsService::MissingRoleError.new('error message') }
+ let(:message) { "Error: No AWS provision role found for user" }
include_examples 'bad request'
end
diff --git a/spec/services/clusters/aws/fetch_credentials_service_spec.rb b/spec/services/clusters/aws/fetch_credentials_service_spec.rb
index 361a947f634..0358ca1f535 100644
--- a/spec/services/clusters/aws/fetch_credentials_service_spec.rb
+++ b/spec/services/clusters/aws/fetch_credentials_service_spec.rb
@@ -60,9 +60,7 @@ RSpec.describe Clusters::Aws::FetchCredentialsService do
subject { described_class.new(provision_role, provider: provider).execute }
before do
- allow(File).to receive(:read)
- .with(Rails.root.join('vendor', 'aws', 'iam', 'eks_cluster_read_only_policy.json'))
- .and_return(session_policy)
+ stub_file_read(Rails.root.join('vendor', 'aws', 'iam', 'eks_cluster_read_only_policy.json'), content: session_policy)
end
it { is_expected.to eq assumed_role_credentials }
@@ -83,5 +81,59 @@ RSpec.describe Clusters::Aws::FetchCredentialsService do
expect { subject }.to raise_error(described_class::MissingRoleError, 'AWS provisioning role not configured')
end
end
+
+ context 'with an instance profile attached to an IAM role' do
+ let(:sts_client) { Aws::STS::Client.new(region: region, stub_responses: true) }
+ let(:provision_role) { create(:aws_role, user: user, region: 'custom-region') }
+
+ before do
+ stub_application_setting(eks_access_key_id: nil)
+ stub_application_setting(eks_secret_access_key: nil)
+
+ expect(Aws::STS::Client).to receive(:new)
+ .with(region: region)
+ .and_return(sts_client)
+
+ expect(Aws::AssumeRoleCredentials).to receive(:new)
+ .with(
+ client: sts_client,
+ role_arn: provision_role.role_arn,
+ role_session_name: session_name,
+ external_id: provision_role.role_external_id,
+ policy: session_policy
+ ).and_call_original
+ end
+
+ context 'provider is specified' do
+ let(:region) { provider.region }
+ let(:session_name) { "gitlab-eks-cluster-#{provider.cluster_id}-user-#{user.id}" }
+ let(:session_policy) { nil }
+
+ it 'returns credentials', :aggregate_failures do
+ expect(subject.access_key_id).to be_present
+ expect(subject.secret_access_key).to be_present
+ expect(subject.session_token).to be_present
+ end
+ end
+
+ context 'provider is not specifed' do
+ let(:provider) { nil }
+ let(:region) { provision_role.region }
+ let(:session_name) { "gitlab-eks-autofill-user-#{user.id}" }
+ let(:session_policy) { 'policy-document' }
+
+ before do
+ stub_file_read(Rails.root.join('vendor', 'aws', 'iam', 'eks_cluster_read_only_policy.json'), content: session_policy)
+ end
+
+ subject { described_class.new(provision_role, provider: provider).execute }
+
+ it 'returns credentials', :aggregate_failures do
+ expect(subject.access_key_id).to be_present
+ expect(subject.secret_access_key).to be_present
+ expect(subject.session_token).to be_present
+ end
+ end
+ end
end
end
diff --git a/spec/services/clusters/aws/provision_service_spec.rb b/spec/services/clusters/aws/provision_service_spec.rb
index 52612e5ac40..5efac29ec1e 100644
--- a/spec/services/clusters/aws/provision_service_spec.rb
+++ b/spec/services/clusters/aws/provision_service_spec.rb
@@ -42,9 +42,7 @@ RSpec.describe Clusters::Aws::ProvisionService do
allow(provider).to receive(:api_client)
.and_return(client)
- allow(File).to receive(:read)
- .with(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
- .and_return(cloudformation_template)
+ stub_file_read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'), content: cloudformation_template)
end
it 'updates the provider status to :creating and configures the provider with credentials' do
diff --git a/spec/services/clusters/cleanup/app_service_spec.rb b/spec/services/clusters/cleanup/app_service_spec.rb
index ba1be7448a4..ea1194d2100 100644
--- a/spec/services/clusters/cleanup/app_service_spec.rb
+++ b/spec/services/clusters/cleanup/app_service_spec.rb
@@ -67,7 +67,8 @@ RSpec.describe Clusters::Cleanup::AppService do
it 'only uninstalls apps that are not dependencies for other installed apps' do
expect(Clusters::Applications::UninstallWorker)
- .not_to receive(:perform_async).with(helm.name, helm.id)
+ .to receive(:perform_async).with(helm.name, helm.id)
+ .and_call_original
expect(Clusters::Applications::UninstallWorker)
.not_to receive(:perform_async).with(ingress.name, ingress.id)
@@ -85,7 +86,7 @@ RSpec.describe Clusters::Cleanup::AppService do
it 'logs application uninstalls and next execution' do
expect(logger).to receive(:info)
- .with(log_meta.merge(event: :uninstalling_app, application: kind_of(String))).twice
+ .with(log_meta.merge(event: :uninstalling_app, application: kind_of(String))).exactly(3).times
expect(logger).to receive(:info)
.with(log_meta.merge(event: :scheduling_execution, next_execution: 1))
diff --git a/spec/services/concerns/exclusive_lease_guard_spec.rb b/spec/services/concerns/exclusive_lease_guard_spec.rb
index d54ba6abadd..6a2aa0a377b 100644
--- a/spec/services/concerns/exclusive_lease_guard_spec.rb
+++ b/spec/services/concerns/exclusive_lease_guard_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe ExclusiveLeaseGuard, :clean_gitlab_redis_shared_state do
it 'does not call internal_method but logs error', :aggregate_failures do
expect(subject).not_to receive(:internal_method)
- expect(Gitlab::AppLogger).to receive(:error).with("Cannot obtain an exclusive lease for #{subject.class.name}. There must be another instance already in execution.")
+ expect(Gitlab::AppLogger).to receive(:error).with("Cannot obtain an exclusive lease for #{subject.lease_key}. There must be another instance already in execution.")
subject.call
end
diff --git a/spec/services/container_expiration_policies/cleanup_service_spec.rb b/spec/services/container_expiration_policies/cleanup_service_spec.rb
index 2da35cfc3fb..8438073ceb0 100644
--- a/spec/services/container_expiration_policies/cleanup_service_spec.rb
+++ b/spec/services/container_expiration_policies/cleanup_service_spec.rb
@@ -28,6 +28,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
expect(ContainerRepository.waiting_for_cleanup.count).to eq(0)
expect(repository.reload.cleanup_unscheduled?).to be_truthy
expect(repository.expiration_policy_started_at).to eq(nil)
+ expect(repository.expiration_policy_completed_at).not_to eq(nil)
end
end
end
@@ -45,6 +46,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
expect(repository.reload.cleanup_unfinished?).to be_truthy
expect(repository.expiration_policy_started_at).not_to eq(nil)
+ expect(repository.expiration_policy_completed_at).to eq(nil)
end
end
end
diff --git a/spec/services/dependency_proxy/auth_token_service_spec.rb b/spec/services/dependency_proxy/auth_token_service_spec.rb
new file mode 100644
index 00000000000..4b96f9d75a9
--- /dev/null
+++ b/spec/services/dependency_proxy/auth_token_service_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe DependencyProxy::AuthTokenService do
+ include DependencyProxyHelpers
+
+ describe '.decoded_token_payload' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:token) { build_jwt(user) }
+
+ subject { described_class.decoded_token_payload(token.encoded) }
+
+ it 'returns the user' do
+ result = subject
+
+ expect(result['user_id']).to eq(user.id)
+ end
+
+ it 'raises an error if the token is expired' do
+ travel_to(Time.zone.now + Auth::DependencyProxyAuthenticationService.token_expire_at + 1.minute) do
+ expect { subject }.to raise_error(JWT::ExpiredSignature)
+ end
+ end
+
+ it 'raises an error if decoding fails' do
+ allow(JWT).to receive(:decode).and_raise(JWT::DecodeError)
+
+ expect { subject }.to raise_error(JWT::DecodeError)
+ end
+
+ it 'raises an error if signature is immature' do
+ allow(JWT).to receive(:decode).and_raise(JWT::ImmatureSignature)
+
+ expect { subject }.to raise_error(JWT::ImmatureSignature)
+ end
+ end
+end
diff --git a/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb b/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb
new file mode 100644
index 00000000000..c375e5a2fa3
--- /dev/null
+++ b/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe DependencyProxy::FindOrCreateManifestService do
+ include DependencyProxyHelpers
+
+ let_it_be(:image) { 'alpine' }
+ let_it_be(:tag) { 'latest' }
+ let_it_be(:dependency_proxy_manifest) { create(:dependency_proxy_manifest, file_name: "#{image}:#{tag}.json") }
+ let(:manifest) { dependency_proxy_manifest.file.read }
+ let(:group) { dependency_proxy_manifest.group }
+ let(:token) { Digest::SHA256.hexdigest('123') }
+ let(:headers) { { 'docker-content-digest' => dependency_proxy_manifest.digest } }
+
+ describe '#execute' do
+ subject { described_class.new(group, image, tag, token).execute }
+
+ context 'when no manifest exists' do
+ let_it_be(:image) { 'new-image' }
+
+ before do
+ stub_manifest_head(image, tag, digest: dependency_proxy_manifest.digest)
+ stub_manifest_download(image, tag, headers: headers)
+ end
+
+ it 'downloads manifest from remote registry if there is no cached one', :aggregate_failures do
+ expect { subject }.to change { group.dependency_proxy_manifests.count }.by(1)
+ expect(subject[:status]).to eq(:success)
+ expect(subject[:manifest]).to be_a(DependencyProxy::Manifest)
+ expect(subject[:manifest]).to be_persisted
+ end
+ end
+
+ context 'when manifest exists' do
+ before do
+ stub_manifest_head(image, tag, digest: dependency_proxy_manifest.digest)
+ end
+
+ shared_examples 'using the cached manifest' do
+ it 'uses cached manifest instead of downloading one', :aggregate_failures do
+ expect(subject[:status]).to eq(:success)
+ expect(subject[:manifest]).to be_a(DependencyProxy::Manifest)
+ expect(subject[:manifest]).to eq(dependency_proxy_manifest)
+ end
+ end
+
+ it_behaves_like 'using the cached manifest'
+
+ context 'when digest is stale' do
+ let(:digest) { 'new-digest' }
+
+ before do
+ stub_manifest_head(image, tag, digest: digest)
+ stub_manifest_download(image, tag, headers: { 'docker-content-digest' => digest })
+ end
+
+ it 'downloads the new manifest and updates the existing record', :aggregate_failures do
+ expect(subject[:status]).to eq(:success)
+ expect(subject[:manifest]).to eq(dependency_proxy_manifest)
+ expect(subject[:manifest].digest).to eq(digest)
+ end
+ end
+
+ context 'failed connection' do
+ before do
+ expect(DependencyProxy::HeadManifestService).to receive(:new).and_raise(Net::OpenTimeout)
+ end
+
+ it_behaves_like 'using the cached manifest'
+
+ context 'and no manifest is cached' do
+ let_it_be(:image) { 'new-image' }
+
+ it 'returns an error', :aggregate_failures do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:http_status]).to eq(503)
+ expect(subject[:message]).to eq('Failed to download the manifest from the external registry')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/dependency_proxy/head_manifest_service_spec.rb b/spec/services/dependency_proxy/head_manifest_service_spec.rb
new file mode 100644
index 00000000000..7c7ebe4d181
--- /dev/null
+++ b/spec/services/dependency_proxy/head_manifest_service_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe DependencyProxy::HeadManifestService do
+ include DependencyProxyHelpers
+
+ let(:image) { 'alpine' }
+ let(:tag) { 'latest' }
+ let(:token) { Digest::SHA256.hexdigest('123') }
+ let(:digest) { '12345' }
+
+ subject { described_class.new(image, tag, token).execute }
+
+ context 'remote request is successful' do
+ before do
+ stub_manifest_head(image, tag, digest: digest)
+ end
+
+ it { expect(subject[:status]).to eq(:success) }
+ it { expect(subject[:digest]).to eq(digest) }
+ end
+
+ context 'remote request is not found' do
+ before do
+ stub_manifest_head(image, tag, status: 404, body: 'Not found')
+ end
+
+ it { expect(subject[:status]).to eq(:error) }
+ it { expect(subject[:http_status]).to eq(404) }
+ it { expect(subject[:message]).to eq('Not found') }
+ end
+
+ context 'net timeout exception' do
+ before do
+ manifest_link = DependencyProxy::Registry.manifest_url(image, tag)
+
+ stub_full_request(manifest_link, method: :head).to_timeout
+ end
+
+ it { expect(subject[:status]).to eq(:error) }
+ it { expect(subject[:http_status]).to eq(599) }
+ it { expect(subject[:message]).to eq('execution expired') }
+ end
+end
diff --git a/spec/services/dependency_proxy/pull_manifest_service_spec.rb b/spec/services/dependency_proxy/pull_manifest_service_spec.rb
index 030ed9c001b..b760839d1fb 100644
--- a/spec/services/dependency_proxy/pull_manifest_service_spec.rb
+++ b/spec/services/dependency_proxy/pull_manifest_service_spec.rb
@@ -8,26 +8,43 @@ RSpec.describe DependencyProxy::PullManifestService do
let(:tag) { '3.9' }
let(:token) { Digest::SHA256.hexdigest('123') }
let(:manifest) { { foo: 'bar' }.to_json }
+ let(:digest) { '12345' }
+ let(:headers) { { 'docker-content-digest' => digest } }
- subject { described_class.new(image, tag, token).execute }
+ subject { described_class.new(image, tag, token).execute_with_manifest(&method(:check_response)) }
context 'remote request is successful' do
before do
- stub_manifest_download(image, tag)
+ stub_manifest_download(image, tag, headers: headers)
end
- it { expect(subject[:status]).to eq(:success) }
- it { expect(subject[:manifest]).to eq(manifest) }
+ it 'successfully returns the manifest' do
+ def check_response(response)
+ response[:file].rewind
+
+ expect(response[:status]).to eq(:success)
+ expect(response[:file].read).to eq(manifest)
+ expect(response[:digest]).to eq(digest)
+ end
+
+ subject
+ end
end
context 'remote request is not found' do
before do
- stub_manifest_download(image, tag, 404, 'Not found')
+ stub_manifest_download(image, tag, status: 404, body: 'Not found')
end
- it { expect(subject[:status]).to eq(:error) }
- it { expect(subject[:http_status]).to eq(404) }
- it { expect(subject[:message]).to eq('Not found') }
+ it 'returns a 404 not found error' do
+ def check_response(response)
+ expect(response[:status]).to eq(:error)
+ expect(response[:http_status]).to eq(404)
+ expect(response[:message]).to eq('Not found')
+ end
+
+ subject
+ end
end
context 'net timeout exception' do
@@ -37,8 +54,20 @@ RSpec.describe DependencyProxy::PullManifestService do
stub_full_request(manifest_link).to_timeout
end
- it { expect(subject[:status]).to eq(:error) }
- it { expect(subject[:http_status]).to eq(599) }
- it { expect(subject[:message]).to eq('execution expired') }
+ it 'returns a 599 error' do
+ def check_response(response)
+ expect(response[:status]).to eq(:error)
+ expect(response[:http_status]).to eq(599)
+ expect(response[:message]).to eq('execution expired')
+ end
+
+ subject
+ end
+ end
+
+ context 'no block is given' do
+ subject { described_class.new(image, tag, token).execute_with_manifest }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'Block must be provided') }
end
end
diff --git a/spec/services/environments/canary_ingress/update_service_spec.rb b/spec/services/environments/canary_ingress/update_service_spec.rb
new file mode 100644
index 00000000000..31d6f543817
--- /dev/null
+++ b/spec/services/environments/canary_ingress/update_service_spec.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Environments::CanaryIngress::UpdateService, :clean_gitlab_redis_cache do
+ include KubernetesHelpers
+
+ let_it_be(:project, refind: true) { create(:project) }
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let(:user) { maintainer }
+ let(:params) { {} }
+ let(:service) { described_class.new(project, user, params) }
+
+ before_all do
+ project.add_maintainer(maintainer)
+ project.add_reporter(reporter)
+ end
+
+ shared_examples_for 'failed request' do
+ it 'returns an error' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq(message)
+ end
+ end
+
+ describe '#execute_async' do
+ subject { service.execute_async(environment) }
+
+ let(:environment) { create(:environment, project: project) }
+ let(:params) { { weight: 50 } }
+ let(:canary_ingress) { ::Gitlab::Kubernetes::Ingress.new(kube_ingress(track: :canary)) }
+
+ context 'when canary_ingress_weight_control feature flag is disabled' do
+ before do
+ stub_feature_flags(canary_ingress_weight_control: false)
+ end
+
+ it_behaves_like 'failed request' do
+ let(:message) { "Feature flag is not enabled on the environment's project." }
+ end
+ end
+
+ context 'when the actor does not have permission to update environment' do
+ let(:user) { reporter }
+
+ it_behaves_like 'failed request' do
+ let(:message) { "You do not have permission to update the environment." }
+ end
+ end
+
+ context 'when weight parameter is invalid' do
+ let(:params) { { weight: 'unknown' } }
+
+ it_behaves_like 'failed request' do
+ let(:message) { 'Canary weight must be specified and valid range (0..100).' }
+ end
+ end
+
+ context 'when no parameters exist' do
+ let(:params) { {} }
+
+ it_behaves_like 'failed request' do
+ let(:message) { 'Canary weight must be specified and valid range (0..100).' }
+ end
+ end
+
+ context 'when environment has a running deployment' do
+ before do
+ allow(environment).to receive(:has_running_deployments?) { true }
+ end
+
+ it_behaves_like 'failed request' do
+ let(:message) { 'There are running deployments on the environment. Please retry later.' }
+ end
+ end
+
+ context 'when canary ingress was updated recently' do
+ before do
+ allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?) { true }
+ end
+
+ it_behaves_like 'failed request' do
+ let(:message) { "This environment's canary ingress has been updated recently. Please retry later." }
+ end
+ end
+ end
+
+ describe '#execute' do
+ subject { service.execute(environment) }
+
+ let(:environment) { create(:environment, project: project) }
+ let(:params) { { weight: 50 } }
+ let(:canary_ingress) { ::Gitlab::Kubernetes::Ingress.new(kube_ingress(track: :canary)) }
+
+ context 'when canary ingress is present in the environment' do
+ before do
+ allow(environment).to receive(:ingresses) { [canary_ingress] }
+ end
+
+ context 'when patch request succeeds' do
+ let(:patch_data) do
+ {
+ metadata: {
+ annotations: {
+ Gitlab::Kubernetes::Ingress::ANNOTATION_KEY_CANARY_WEIGHT => params[:weight].to_s
+ }
+ }
+ }
+ end
+
+ before do
+ allow(environment).to receive(:patch_ingress).with(canary_ingress, patch_data) { true }
+ end
+
+ it 'returns success' do
+ expect(subject[:status]).to eq(:success)
+ expect(subject[:message]).to be_nil
+ end
+ end
+
+ context 'when patch request does not succeed' do
+ before do
+ allow(environment).to receive(:patch_ingress) { false }
+ end
+
+ it_behaves_like 'failed request' do
+ let(:message) { 'Failed to update the Canary Ingress.' }
+ end
+ end
+ end
+
+ context 'when canary ingress is not present in the environment' do
+ it_behaves_like 'failed request' do
+ let(:message) { 'Canary Ingress does not exist in the environment.' }
+ end
+ end
+ end
+end
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index 3c67c15f10a..17b2c7b38e1 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe EventCreateService do
tracking_params = { event_action: event_action, date_from: Date.yesterday, date_to: Date.today }
expect { subject }
- .to change { Gitlab::UsageDataCounters::TrackUniqueEvents.count_unique_events(tracking_params) }
+ .to change { Gitlab::UsageDataCounters::TrackUniqueEvents.count_unique_events(**tracking_params) }
.by(1)
end
end
@@ -386,7 +386,7 @@ RSpec.describe EventCreateService do
counter_class = Gitlab::UsageDataCounters::TrackUniqueEvents
tracking_params = { event_action: event_action, date_from: Date.yesterday, date_to: Date.today }
- expect { subject }.not_to change { counter_class.count_unique_events(tracking_params) }
+ expect { subject }.not_to change { counter_class.count_unique_events(**tracking_params) }
end
end
end
diff --git a/spec/services/files/delete_service_spec.rb b/spec/services/files/delete_service_spec.rb
index 17e4645fde6..3823d027812 100644
--- a/spec/services/files/delete_service_spec.rb
+++ b/spec/services/files/delete_service_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe Files::DeleteService do
context "when the file's last commit sha does not match the supplied last_commit_sha" do
let(:last_commit_sha) { "foo" }
- it "returns a hash with the correct error message and a :error status " do
+ it "returns a hash with the correct error message and a :error status" do
expect { subject.execute }
.to raise_error(Files::UpdateService::FileChangedError,
"You are attempting to delete a file that has been previously updated.")
diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb
index 84d78b4c2bc..6d7459e0b29 100644
--- a/spec/services/files/update_service_spec.rb
+++ b/spec/services/files/update_service_spec.rb
@@ -34,7 +34,7 @@ RSpec.describe Files::UpdateService do
context "when the file's last commit sha does not match the supplied last_commit_sha" do
let(:last_commit_sha) { "foo" }
- it "returns a hash with the correct error message and a :error status " do
+ it "returns a hash with the correct error message and a :error status" do
expect { subject.execute }
.to raise_error(Files::UpdateService::FileChangedError,
"You are attempting to update a file that has changed since you started editing it.")
@@ -44,7 +44,7 @@ RSpec.describe Files::UpdateService do
context "when the file's last commit sha does match the supplied last_commit_sha" do
let(:last_commit_sha) { Gitlab::Git::Commit.last_for_path(project.repository, project.default_branch, file_path).sha }
- it "returns a hash with the :success status " do
+ it "returns a hash with the :success status" do
results = subject.execute
expect(results[:status]).to match(:success)
@@ -68,7 +68,7 @@ RSpec.describe Files::UpdateService do
end
context "when the last_commit_sha is not supplied" do
- it "returns a hash with the :success status " do
+ it "returns a hash with the :success status" do
results = subject.execute
expect(results[:status]).to match(:success)
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index 5d73794c1ec..c7bf006dab0 100644
--- a/spec/services/git/branch_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -718,10 +718,10 @@ RSpec.describe Git::BranchPushService, services: true do
end
shared_examples 'enqueues Jira sync worker' do
- specify do
+ specify :aggregate_failures do
Sidekiq::Testing.fake! do
expect(JiraConnect::SyncBranchWorker).to receive(:perform_async)
- .with(project.id, branch_to_sync, commits_to_sync)
+ .with(project.id, branch_to_sync, commits_to_sync, kind_of(Numeric))
.and_call_original
expect { subject.execute }.to change(JiraConnect::SyncBranchWorker.jobs, :size).by(1)
diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb
index 4443c46a414..dae2f63f2f9 100644
--- a/spec/services/git/tag_hooks_service_spec.rb
+++ b/spec/services/git/tag_hooks_service_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Git::TagHooksService, :service do
end
describe 'System hooks' do
- it 'Executes system hooks' do
+ it 'executes system hooks' do
push_data = service.send(:push_data)
expect(project).to receive(:has_active_hooks?).and_return(true)
diff --git a/spec/services/groups/import_export/import_service_spec.rb b/spec/services/groups/import_export/import_service_spec.rb
index f8cb55a9955..0c7765dcd38 100644
--- a/spec/services/groups/import_export/import_service_spec.rb
+++ b/spec/services/groups/import_export/import_service_spec.rb
@@ -110,6 +110,7 @@ RSpec.describe Groups::ImportExport::ImportService do
allow(Gitlab::Import::Logger).to receive(:build).and_return(import_logger)
allow(import_logger).to receive(:error)
allow(import_logger).to receive(:info)
+ allow(import_logger).to receive(:warn)
allow(FileUtils).to receive(:rm_rf).and_call_original
end
@@ -220,6 +221,7 @@ RSpec.describe Groups::ImportExport::ImportService do
allow(Gitlab::Import::Logger).to receive(:build).and_return(import_logger)
allow(import_logger).to receive(:error)
+ allow(import_logger).to receive(:warn)
allow(import_logger).to receive(:info)
allow(FileUtils).to receive(:rm_rf).and_call_original
end
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index ae04eca3a9f..19b746ade34 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -3,15 +3,15 @@
require 'spec_helper'
RSpec.describe Groups::TransferService do
- let(:user) { create(:user) }
- let(:new_parent_group) { create(:group, :public) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:new_parent_group) { create(:group, :public) }
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
let(:transfer_service) { described_class.new(group, user) }
context 'handling packages' do
let_it_be(:group) { create(:group, :public) }
+ let_it_be(:new_group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) }
- let(:new_group) { create(:group, :public) }
before do
group.add_owner(user)
@@ -35,8 +35,8 @@ RSpec.describe Groups::TransferService do
it_behaves_like 'transfer not allowed'
context 'with a project within subgroup' do
- let(:root_group) { create(:group) }
- let(:group) { create(:group, parent: root_group) }
+ let_it_be(:root_group) { create(:group) }
+ let_it_be(:group) { create(:group, parent: root_group) }
before do
root_group.add_owner(user)
@@ -79,8 +79,6 @@ RSpec.describe Groups::TransferService do
shared_examples 'ensuring allowed transfer for a group' do
context "when there's an exception on GitLab shell directories" do
- let(:new_parent_group) { create(:group, :public) }
-
before do
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:update_group_attributes).and_raise(Gitlab::UpdatePathError, 'namespace directory cannot be moved')
@@ -101,7 +99,7 @@ RSpec.describe Groups::TransferService do
describe '#execute' do
context 'when transforming a group into a root group' do
- let!(:group) { create(:group, :public, :nested) }
+ let_it_be_with_reload(:group) { create(:group, :public, :nested) }
it_behaves_like 'ensuring allowed transfer for a group'
@@ -115,7 +113,7 @@ RSpec.describe Groups::TransferService do
end
context 'when the user does not have the right policies' do
- let!(:group_member) { create(:group_member, :guest, group: group, user: user) }
+ let_it_be(:group_member) { create(:group_member, :guest, group: group, user: user) }
it "returns false" do
expect(transfer_service.execute(nil)).to be_falsy
@@ -128,7 +126,7 @@ RSpec.describe Groups::TransferService do
end
context 'when there is a group with the same path' do
- let!(:group) { create(:group, :public, :nested, path: 'not-unique') }
+ let_it_be(:group) { create(:group, :public, :nested, path: 'not-unique') }
before do
create(:group, path: 'not-unique')
@@ -145,9 +143,9 @@ RSpec.describe Groups::TransferService do
end
context 'when the group is a subgroup and the transfer is valid' do
- let!(:subgroup1) { create(:group, :private, parent: group) }
- let!(:subgroup2) { create(:group, :internal, parent: group) }
- let!(:project1) { create(:project, :repository, :private, namespace: group) }
+ let_it_be(:subgroup1) { create(:group, :private, parent: group) }
+ let_it_be(:subgroup2) { create(:group, :internal, parent: group) }
+ let_it_be(:project1) { create(:project, :repository, :private, namespace: group) }
before do
transfer_service.execute(nil)
@@ -173,12 +171,12 @@ RSpec.describe Groups::TransferService do
end
context 'when transferring a subgroup into another group' do
- let(:group) { create(:group, :public, :nested) }
+ let_it_be_with_reload(:group) { create(:group, :public, :nested) }
it_behaves_like 'ensuring allowed transfer for a group'
context 'when the new parent group is the same as the previous parent group' do
- let(:group) { create(:group, :public, :nested, parent: new_parent_group) }
+ let_it_be(:group) { create(:group, :public, :nested, parent: new_parent_group) }
it 'returns false' do
expect(transfer_service.execute(new_parent_group)).to be_falsy
@@ -191,7 +189,7 @@ RSpec.describe Groups::TransferService do
end
context 'when the user does not have the right policies' do
- let!(:group_member) { create(:group_member, :guest, group: group, user: user) }
+ let_it_be(:group_member) { create(:group_member, :guest, group: group, user: user) }
it "returns false" do
expect(transfer_service.execute(new_parent_group)).to be_falsy
@@ -221,7 +219,7 @@ RSpec.describe Groups::TransferService do
end
context 'when the parent group has a project with the same path' do
- let!(:group) { create(:group, :public, :nested, path: 'foo') }
+ let_it_be_with_reload(:group) { create(:group, :public, :nested, path: 'foo') }
before do
create(:group_member, :owner, group: new_parent_group, user: user)
@@ -240,8 +238,13 @@ RSpec.describe Groups::TransferService do
end
context 'when the group is allowed to be transferred' do
+ let_it_be(:new_parent_group_integration) { create(:slack_service, group: new_parent_group, project: nil, webhook: 'http://new-group.slack.com') }
+
before do
+ allow(PropagateIntegrationWorker).to receive(:perform_async)
+
create(:group_member, :owner, group: new_parent_group, user: user)
+
transfer_service.execute(new_parent_group)
end
@@ -267,6 +270,30 @@ RSpec.describe Groups::TransferService do
end
end
+ context 'with a group integration' do
+ let_it_be(:instance_integration) { create(:slack_service, :instance, webhook: 'http://project.slack.com') }
+ let(:new_created_integration) { Service.find_by(group: group) }
+
+ context 'with an inherited integration' do
+ let_it_be(:group_integration) { create(:slack_service, group: group, project: nil, webhook: 'http://group.slack.com', inherit_from_id: instance_integration.id) }
+
+ it 'replaces inherited integrations', :aggregate_failures do
+ expect(new_created_integration.webhook).to eq(new_parent_group_integration.webhook)
+ expect(PropagateIntegrationWorker).to have_received(:perform_async).with(new_created_integration.id)
+ expect(Service.count).to eq(3)
+ end
+ end
+
+ context 'with a custom integration' do
+ let_it_be(:group_integration) { create(:slack_service, group: group, project: nil, webhook: 'http://group.slack.com') }
+
+ it 'does not updates the integrations', :aggregate_failures do
+ expect { transfer_service.execute(new_parent_group) }.not_to change { group_integration.webhook }
+ expect(PropagateIntegrationWorker).not_to have_received(:perform_async)
+ end
+ end
+ end
+
it 'updates visibility for the group based on the parent group' do
expect(group.visibility_level).to eq(new_parent_group.visibility_level)
end
@@ -464,7 +491,7 @@ RSpec.describe Groups::TransferService do
end
context 'updated paths' do
- let(:group) { create(:group, :public) }
+ let_it_be_with_reload(:group) { create(:group, :public) }
before do
transfer_service.execute(new_parent_group)
@@ -500,10 +527,10 @@ RSpec.describe Groups::TransferService do
end
context 'resets project authorizations' do
- let(:old_parent_group) { create(:group) }
- let(:group) { create(:group, :private, parent: old_parent_group) }
- let(:new_group_member) { create(:user) }
- let(:old_group_member) { create(:user) }
+ let_it_be(:old_parent_group) { create(:group) }
+ let_it_be_with_reload(:group) { create(:group, :private, parent: old_parent_group) }
+ let_it_be(:new_group_member) { create(:user) }
+ let_it_be(:old_group_member) { create(:user) }
before do
new_parent_group.add_maintainer(new_group_member)
diff --git a/spec/services/incident_management/incidents/create_service_spec.rb b/spec/services/incident_management/incidents/create_service_spec.rb
index 1330f3ae033..4601bd807d0 100644
--- a/spec/services/incident_management/incidents/create_service_spec.rb
+++ b/spec/services/incident_management/incidents/create_service_spec.rb
@@ -37,6 +37,8 @@ RSpec.describe IncidentManagement::Incidents::CreateService do
end
let(:issue) { new_issue }
+
+ include_examples 'has incident label'
end
context 'with default severity' do
diff --git a/spec/services/issues/clone_service_spec.rb b/spec/services/issues/clone_service_spec.rb
new file mode 100644
index 00000000000..512a60b1382
--- /dev/null
+++ b/spec/services/issues/clone_service_spec.rb
@@ -0,0 +1,340 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Issues::CloneService do
+ include DesignManagementTestHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:author) { create(:user) }
+ let_it_be(:title) { 'Some issue' }
+ let_it_be(:description) { "Some issue description with mention to #{user.to_reference}" }
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:sub_group_1) { create(:group, :private, parent: group) }
+ let_it_be(:sub_group_2) { create(:group, :private, parent: group) }
+ let_it_be(:old_project) { create(:project, namespace: sub_group_1) }
+ let_it_be(:new_project) { create(:project, namespace: sub_group_2) }
+
+ let_it_be(:old_issue, reload: true) do
+ create(:issue, title: title, description: description, project: old_project, author: author)
+ end
+
+ let(:with_notes) { false }
+
+ subject(:clone_service) do
+ described_class.new(old_project, user)
+ end
+
+ shared_context 'user can clone issue' do
+ before do
+ old_project.add_reporter(user)
+ new_project.add_reporter(user)
+ end
+ end
+
+ describe '#execute' do
+ context 'issue movable' do
+ include_context 'user can clone issue'
+
+ context 'generic issue' do
+ let!(:new_issue) { clone_service.execute(old_issue, new_project, with_notes: with_notes) }
+
+ it 'creates a new issue in the selected project' do
+ expect do
+ clone_service.execute(old_issue, new_project)
+ end.to change { new_project.issues.count }.by(1)
+ end
+
+ it 'copies issue title' do
+ expect(new_issue.title).to eq title
+ end
+
+ it 'copies issue description' do
+ expect(new_issue.description).to eq description
+ end
+
+ it 'adds system note to old issue at the end' do
+ expect(old_issue.notes.last.note).to start_with 'cloned to'
+ end
+
+ it 'adds system note to new issue at the end' do
+ expect(new_issue.notes.last.note).to start_with 'cloned from'
+ end
+
+ it 'keeps old issue open' do
+ expect(old_issue.open?).to be true
+ end
+
+ it 'persists new issue' do
+ expect(new_issue.persisted?).to be true
+ end
+
+ it 'persists all changes' do
+ expect(old_issue.changed?).to be false
+ expect(new_issue.changed?).to be false
+ end
+
+ it 'sets the current user as author' do
+ expect(new_issue.author).to eq user
+ end
+
+ it 'creates a new internal id for issue' do
+ expect(new_issue.iid).to be_present
+ end
+
+ it 'preserves create time' do
+ expect(old_issue.created_at.strftime('%D')).to eq new_issue.created_at.strftime('%D')
+ end
+
+ it 'does not copy system notes' do
+ expect(new_issue.notes.count).to eq(1)
+ end
+
+ it 'does not set moved_issue' do
+ expect(old_issue.moved?).to eq(false)
+ end
+
+ context 'when copying comments' do
+ let(:with_notes) { true }
+
+ it 'does not create extra system notes' do
+ new_issue = clone_service.execute(old_issue, new_project, with_notes: with_notes)
+
+ expect(new_issue.notes.count).to eq(old_issue.notes.count)
+ end
+ end
+ end
+
+ context 'issue with award emoji' do
+ let!(:award_emoji) { create(:award_emoji, awardable: old_issue) }
+
+ it 'copies the award emoji' do
+ old_issue.reload
+ new_issue = clone_service.execute(old_issue, new_project)
+
+ expect(old_issue.award_emoji.first.name).to eq new_issue.reload.award_emoji.first.name
+ end
+ end
+
+ context 'issue with milestone' do
+ let(:milestone) { create(:milestone, group: sub_group_1) }
+ let(:new_project) { create(:project, namespace: sub_group_1) }
+
+ let(:old_issue) do
+ create(:issue, title: title, description: description, project: old_project, author: author, milestone: milestone)
+ end
+
+ before do
+ create(:resource_milestone_event, issue: old_issue, milestone: milestone, action: :add)
+ end
+
+ it 'does not create extra milestone events' do
+ new_issue = clone_service.execute(old_issue, new_project)
+
+ expect(new_issue.resource_milestone_events.count).to eq(old_issue.resource_milestone_events.count)
+ end
+ end
+
+ context 'issue with due date' do
+ let(:date) { Date.parse('2020-01-10') }
+
+ let(:old_issue) do
+ create(:issue, title: title, description: description, project: old_project, author: author, due_date: date)
+ end
+
+ before do
+ SystemNoteService.change_due_date(old_issue, old_project, author, old_issue.due_date)
+ end
+
+ it 'keeps the same due date' do
+ new_issue = clone_service.execute(old_issue, new_project)
+
+ expect(new_issue.due_date).to eq(date)
+ end
+ end
+
+ context 'issue with assignee' do
+ let_it_be(:assignee) { create(:user) }
+
+ before do
+ old_issue.assignees = [assignee]
+ end
+
+ it 'preserves assignee with access to the new issue' do
+ new_project.add_reporter(assignee)
+
+ new_issue = clone_service.execute(old_issue, new_project)
+
+ expect(new_issue.assignees).to eq([assignee])
+ end
+
+ it 'ignores assignee without access to the new issue' do
+ new_issue = clone_service.execute(old_issue, new_project)
+
+ expect(new_issue.assignees).to be_empty
+ end
+ end
+
+ context 'issue is confidential' do
+ before do
+ old_issue.update_columns(confidential: true)
+ end
+
+ it 'preserves the confidential flag' do
+ new_issue = clone_service.execute(old_issue, new_project)
+
+ expect(new_issue.confidential).to be true
+ end
+ end
+
+ context 'moving to same project' do
+ it 'also works' do
+ new_issue = clone_service.execute(old_issue, old_project)
+
+ expect(new_issue.project).to eq(old_project)
+ expect(new_issue.iid).not_to eq(old_issue.iid)
+ end
+ end
+
+ context 'project issue hooks' do
+ let!(:hook) { create(:project_hook, project: old_project, issues_events: true) }
+
+ it 'executes project issue hooks' do
+ allow_next_instance_of(WebHookService) do |instance|
+ allow(instance).to receive(:execute)
+ end
+
+ # Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1,
+ # but since the entire spec run takes place in a transaction, we never
+ # actually get to the `after_commit` hook that queues these jobs.
+ expect { clone_service.execute(old_issue, new_project) }
+ .not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError
+ end
+ end
+
+ # These tests verify that notes are copied. More thorough tests are in
+ # the unit test for Notes::CopyService.
+ context 'issue with notes' do
+ let_it_be(:notes) do
+ [
+ create(:note, noteable: old_issue, project: old_project, created_at: 2.weeks.ago, updated_at: 1.week.ago),
+ create(:note, noteable: old_issue, project: old_project)
+ ]
+ end
+
+ let(:new_issue) { clone_service.execute(old_issue, new_project, with_notes: with_notes) }
+
+ let(:copied_notes) { new_issue.notes.limit(notes.size) } # Remove the system note added by the copy itself
+
+ it 'does not copy notes' do
+ # only the system note
+ expect(copied_notes.order('id ASC').pluck(:note).size).to eq(1)
+ end
+
+ context 'when copying comments' do
+ let(:with_notes) { true }
+
+ it 'copies existing notes in order' do
+ expect(copied_notes.order('id ASC').pluck(:note)).to eq(notes.map(&:note))
+ end
+ end
+ end
+
+ context 'issue with a design', :clean_gitlab_redis_shared_state do
+ let_it_be(:new_project) { create(:project) }
+ let!(:design) { create(:design, :with_lfs_file, issue: old_issue) }
+ let!(:note) { create(:diff_note_on_design, noteable: design, issue: old_issue, project: old_issue.project) }
+ let(:subject) { clone_service.execute(old_issue, new_project) }
+
+ before do
+ enable_design_management
+ end
+
+ it 'calls CopyDesignCollection::QueueService' do
+ expect(DesignManagement::CopyDesignCollection::QueueService).to receive(:new)
+ .with(user, old_issue, kind_of(Issue))
+ .and_call_original
+
+ subject
+ end
+
+ it 'logs if QueueService returns an error', :aggregate_failures do
+ error_message = 'error'
+
+ expect_next_instance_of(DesignManagement::CopyDesignCollection::QueueService) do |service|
+ expect(service).to receive(:execute).and_return(
+ ServiceResponse.error(message: error_message)
+ )
+ end
+ expect(Gitlab::AppLogger).to receive(:error).with(error_message)
+
+ subject
+ end
+
+ # Perform a small integration test to ensure the services and worker
+ # can correctly create designs.
+ it 'copies the design and its notes', :sidekiq_inline, :aggregate_failures do
+ new_issue = subject
+
+ expect(new_issue.designs.size).to eq(1)
+ expect(new_issue.designs.first.notes.size).to eq(1)
+ end
+ end
+ end
+
+ describe 'clone permissions' do
+ let(:clone) { clone_service.execute(old_issue, new_project) }
+
+ context 'target project is pending deletion' do
+ include_context 'user can clone issue'
+
+ before do
+ new_project.update_columns(pending_delete: true)
+ end
+
+ after do
+ new_project.update_columns(pending_delete: false)
+ end
+
+ it { expect { clone }.to raise_error(Issues::CloneService::CloneError, /pending deletion/) }
+ end
+
+ context 'user is reporter in both projects' do
+ include_context 'user can clone issue'
+ it { expect { clone }.not_to raise_error }
+ end
+
+ context 'user is reporter only in new project' do
+ before do
+ new_project.add_reporter(user)
+ end
+
+ it { expect { clone }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'user is reporter only in old project' do
+ before do
+ old_project.add_reporter(user)
+ end
+
+ it { expect { clone }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'user is reporter in one project and guest in another' do
+ before do
+ new_project.add_guest(user)
+ old_project.add_reporter(user)
+ end
+
+ it { expect { clone }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'issue is not persisted' do
+ include_context 'user can clone issue'
+ let(:old_issue) { build(:issue, project: old_project, author: author) }
+
+ it { expect { clone }.to raise_error(StandardError, /permissions/) }
+ end
+ end
+ end
+end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index eeac7fb9923..cc6a49fc4cf 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -63,6 +63,7 @@ RSpec.describe Issues::CreateService do
subject { issue }
it_behaves_like 'incident issue'
+ it_behaves_like 'has incident label'
it_behaves_like 'an incident management tracked event', :incident_management_incident_created
it 'does create an incident label' do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index cfda27795c7..06a6a52bc41 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -99,31 +99,18 @@ RSpec.describe Issues::UpdateService, :mailer do
context 'when issue type is incident' do
let(:issue) { create(:incident, project: project) }
- it 'changes updates the severity' do
+ before do
update_issue(opts)
-
- expect(issue.severity).to eq('low')
end
- it_behaves_like 'incident issue' do
- before do
- update_issue(opts)
- end
- end
-
- context 'with existing incident label' do
- let_it_be(:incident_label) { create(:label, :incident, project: project) }
+ it_behaves_like 'incident issue'
- before do
- opts.delete(:label_ids) # don't override but retain existing labels
- issue.labels << incident_label
- end
+ it 'changes updates the severity' do
+ expect(issue.severity).to eq('low')
+ end
- it_behaves_like 'incident issue' do
- before do
- update_issue(opts)
- end
- end
+ it 'does not apply incident labels' do
+ expect(issue.labels).to match_array [label]
end
end
@@ -155,7 +142,6 @@ RSpec.describe Issues::UpdateService, :mailer do
context 'issue in incident type' do
let(:current_user) { user }
- let(:incident_label_attributes) { attributes_for(:label, :incident) }
before do
opts.merge!(issue_type: 'incident', confidential: true)
@@ -170,21 +156,6 @@ RSpec.describe Issues::UpdateService, :mailer do
subject
end
end
-
- it 'does create an incident label' do
- expect { subject }
- .to change { Label.where(incident_label_attributes).count }.by(1)
- end
-
- context 'when invalid' do
- before do
- opts.merge!(title: '')
- end
-
- it 'does not create an incident label prematurely' do
- expect { subject }.not_to change(Label, :count)
- end
- end
end
it 'updates open issue counter for assignees when issue is reassigned' do
@@ -968,6 +939,46 @@ RSpec.describe Issues::UpdateService, :mailer do
end
end
+ context 'clone an issue' do
+ context 'valid project' do
+ let(:target_project) { create(:project) }
+
+ before do
+ target_project.add_maintainer(user)
+ end
+
+ it 'calls the move service with the proper issue and project' do
+ clone_stub = instance_double(Issues::CloneService)
+ allow(Issues::CloneService).to receive(:new).and_return(clone_stub)
+ allow(clone_stub).to receive(:execute).with(issue, target_project, with_notes: nil).and_return(issue)
+
+ expect(clone_stub).to receive(:execute).with(issue, target_project, with_notes: nil)
+
+ update_issue(target_clone_project: target_project)
+ end
+ end
+ end
+
+ context 'clone an issue with notes' do
+ context 'valid project' do
+ let(:target_project) { create(:project) }
+
+ before do
+ target_project.add_maintainer(user)
+ end
+
+ it 'calls the move service with the proper issue and project' do
+ clone_stub = instance_double(Issues::CloneService)
+ allow(Issues::CloneService).to receive(:new).and_return(clone_stub)
+ allow(clone_stub).to receive(:execute).with(issue, target_project, with_notes: true).and_return(issue)
+
+ expect(clone_stub).to receive(:execute).with(issue, target_project, with_notes: true)
+
+ update_issue(target_clone_project: target_project, clone_with_notes: true)
+ end
+ end
+ end
+
context 'when moving an issue ' do
it 'raises an error for invalid move ids within a project' do
opts = { move_between_ids: [9000, non_existing_record_id] }
diff --git a/spec/services/jira/requests/projects/list_service_spec.rb b/spec/services/jira/requests/projects/list_service_spec.rb
index f7bcfa997df..0fff51b1226 100644
--- a/spec/services/jira/requests/projects/list_service_spec.rb
+++ b/spec/services/jira/requests/projects/list_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Jira::Requests::Projects::ListService do
+ include AfterNextHelpers
+
let(:jira_service) { create(:jira_service) }
let(:params) { {} }
@@ -33,15 +35,17 @@ RSpec.describe Jira::Requests::Projects::ListService do
context 'with jira_service' do
context 'when validations and params are ok' do
- let(:client) { double(options: { site: 'https://jira.example.com' }) }
+ let(:response_headers) { { 'content-type' => 'application/json' } }
+ let(:response_body) { [].to_json }
+ let(:expected_url_pattern) { /.*jira.example.com\/rest\/api\/2\/project/ }
before do
- expect(service).to receive(:client).at_least(:once).and_return(client)
+ stub_request(:get, expected_url_pattern).to_return(status: 200, body: response_body, headers: response_headers)
end
context 'when the request to Jira returns an error' do
before do
- expect(client).to receive(:get).and_raise(Timeout::Error)
+ expect_next(JIRA::Client).to receive(:get).and_raise(Timeout::Error)
end
it 'returns an error response' do
@@ -54,10 +58,17 @@ RSpec.describe Jira::Requests::Projects::ListService do
end
end
- context 'when the request does not return any values' do
- before do
- expect(client).to receive(:get).and_return([])
+ context 'when jira runs on a subpath' do
+ let(:jira_service) { create(:jira_service, url: 'http://jira.example.com/jira') }
+ let(:expected_url_pattern) { /.*jira.example.com\/jira\/rest\/api\/2\/project/ }
+
+ it 'takes the subpath into account' do
+ expect(subject.success?).to be_truthy
end
+ end
+
+ context 'when the request does not return any values' do
+ let(:response_body) { [].to_json }
it 'returns a paylod with no projects returned' do
payload = subject.payload
@@ -69,9 +80,7 @@ RSpec.describe Jira::Requests::Projects::ListService do
end
context 'when the request returns values' do
- before do
- expect(client).to receive(:get).and_return([{ 'key' => 'pr1', 'name' => 'First Project' }, { 'key' => 'pr2', 'name' => 'Second Project' }])
- end
+ let(:response_body) { [{ 'key' => 'pr1', 'name' => 'First Project' }, { 'key' => 'pr2', 'name' => 'Second Project' }].to_json }
it 'returns a paylod with Jira projects' do
payload = subject.payload
diff --git a/spec/services/jira_connect/sync_service_spec.rb b/spec/services/jira_connect/sync_service_spec.rb
index 83088bb2e79..4b434348146 100644
--- a/spec/services/jira_connect/sync_service_spec.rb
+++ b/spec/services/jira_connect/sync_service_spec.rb
@@ -3,30 +3,23 @@
require 'spec_helper'
RSpec.describe JiraConnect::SyncService do
+ include AfterNextHelpers
+
describe '#execute' do
let_it_be(:project) { create(:project, :repository) }
- let(:branches) { [project.repository.find_branch('master')] }
- let(:commits) { project.commits_by(oids: %w[b83d6e3 5a62481]) }
- let(:merge_requests) { [create(:merge_request, source_project: project, target_project: project)] }
+ let(:client) { Atlassian::JiraConnect::Client }
+ let(:info) { { a: 'Some', b: 'Info' } }
subject do
- described_class.new(project).execute(commits: commits, branches: branches, merge_requests: merge_requests)
+ described_class.new(project).execute(**info)
end
before do
create(:jira_connect_subscription, namespace: project.namespace)
end
- def expect_jira_client_call(return_value = { 'status': 'success' })
- expect_next_instance_of(Atlassian::JiraConnect::Client) do |instance|
- expect(instance).to receive(:store_dev_info).with(
- project: project,
- commits: commits,
- branches: [instance_of(Gitlab::Git::Branch)],
- merge_requests: merge_requests,
- update_sequence_id: anything
- ).and_return(return_value)
- end
+ def store_info(return_values = [{ 'status': 'success' }])
+ receive(:send_info).with(project: project, **info).and_return(return_values)
end
def expect_log(type, message)
@@ -41,20 +34,22 @@ RSpec.describe JiraConnect::SyncService do
end
it 'calls Atlassian::JiraConnect::Client#store_dev_info and logs the response' do
- expect_jira_client_call
+ expect_next(client).to store_info
expect_log(:info, { 'status': 'success' })
subject
end
- context 'when request returns an error' do
+ context 'when a request returns an error' do
it 'logs the response as an error' do
- expect_jira_client_call({
- 'errorMessages' => ['some error message']
- })
+ expect_next(client).to store_info([
+ { 'errorMessages' => ['some error message'] },
+ { 'rejectedBuilds' => ['x'] }
+ ])
expect_log(:error, { 'errorMessages' => ['some error message'] })
+ expect_log(:error, { 'rejectedBuilds' => ['x'] })
subject
end
diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb
index e6a94fdaf84..f7fbac612ee 100644
--- a/spec/services/members/approve_access_request_service_spec.rb
+++ b/spec/services/members/approve_access_request_service_spec.rb
@@ -12,23 +12,23 @@ RSpec.describe Members::ApproveAccessRequestService do
shared_examples 'a service raising ActiveRecord::RecordNotFound' do
it 'raises ActiveRecord::RecordNotFound' do
- expect { described_class.new(current_user).execute(access_requester, opts) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { described_class.new(current_user).execute(access_requester, **opts) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
it 'raises Gitlab::Access::AccessDeniedError' do
- expect { described_class.new(current_user).execute(access_requester, opts) }.to raise_error(Gitlab::Access::AccessDeniedError)
+ expect { described_class.new(current_user).execute(access_requester, **opts) }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
shared_examples 'a service approving an access request' do
it 'succeeds' do
- expect { described_class.new(current_user).execute(access_requester, opts) }.to change { source.requesters.count }.by(-1)
+ expect { described_class.new(current_user).execute(access_requester, **opts) }.to change { source.requesters.count }.by(-1)
end
it 'returns a <Source>Member' do
- member = described_class.new(current_user).execute(access_requester, opts)
+ member = described_class.new(current_user).execute(access_requester, **opts)
expect(member).to be_a "#{source.class}Member".constantize
expect(member.requested_at).to be_nil
@@ -36,7 +36,7 @@ RSpec.describe Members::ApproveAccessRequestService do
context 'with a custom access level' do
it 'returns a ProjectMember with the custom access level' do
- member = described_class.new(current_user, access_level: Gitlab::Access::MAINTAINER).execute(access_requester, opts)
+ member = described_class.new(current_user, access_level: Gitlab::Access::MAINTAINER).execute(access_requester, **opts)
expect(member.access_level).to eq(Gitlab::Access::MAINTAINER)
end
diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb
index 00b5ff59e48..e8a4a798b20 100644
--- a/spec/services/members/create_service_spec.rb
+++ b/spec/services/members/create_service_spec.rb
@@ -3,59 +3,68 @@
require 'spec_helper'
RSpec.describe Members::CreateService do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:project_user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project_user) { create(:user) }
+ let_it_be(:user_ids) { project_user.id.to_s }
+ let_it_be(:access_level) { Gitlab::Access::GUEST }
+ let(:params) { { user_ids: user_ids, access_level: access_level } }
+
+ subject(:execute_service) { described_class.new(user, params).execute(project) }
before do
project.add_maintainer(user)
+ allow(Namespaces::OnboardingUserAddedWorker).to receive(:idempotent?).and_return(false)
end
- it 'adds user to members' do
- params = { user_ids: project_user.id.to_s, access_level: Gitlab::Access::GUEST }
- result = described_class.new(user, params).execute(project)
-
- expect(result[:status]).to eq(:success)
- expect(project.users).to include project_user
+ context 'when passing valid parameters' do
+ it 'adds a user to members' do
+ expect(execute_service[:status]).to eq(:success)
+ expect(project.users).to include project_user
+ expect(Namespaces::OnboardingUserAddedWorker.jobs.last['args'][0]).to eq(project.id)
+ end
end
- it 'adds no user to members' do
- params = { user_ids: '', access_level: Gitlab::Access::GUEST }
- result = described_class.new(user, params).execute(project)
+ context 'when passing no user ids' do
+ let(:user_ids) { '' }
- expect(result[:status]).to eq(:error)
- expect(result[:message]).to be_present
- expect(project.users).not_to include project_user
+ it 'does not add a member' do
+ expect(execute_service[:status]).to eq(:error)
+ expect(execute_service[:message]).to be_present
+ expect(project.users).not_to include project_user
+ expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0)
+ end
end
- it 'limits the number of users to 100' do
- user_ids = 1.upto(101).to_a.join(',')
- params = { user_ids: user_ids, access_level: Gitlab::Access::GUEST }
+ context 'when passing many user ids' do
+ let(:user_ids) { 1.upto(101).to_a.join(',') }
- result = described_class.new(user, params).execute(project)
-
- expect(result[:status]).to eq(:error)
- expect(result[:message]).to be_present
- expect(project.users).not_to include project_user
+ it 'limits the number of users to 100' do
+ expect(execute_service[:status]).to eq(:error)
+ expect(execute_service[:message]).to be_present
+ expect(project.users).not_to include project_user
+ expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0)
+ end
end
- it 'does not add an invalid member' do
- params = { user_ids: project_user.id.to_s, access_level: -1 }
- result = described_class.new(user, params).execute(project)
+ context 'when passing an invalid access level' do
+ let(:access_level) { -1 }
- expect(result[:status]).to eq(:error)
- expect(result[:message]).to include("#{project_user.username}: Access level is not included in the list")
- expect(project.users).not_to include project_user
+ it 'does not add a member' do
+ expect(execute_service[:status]).to eq(:error)
+ expect(execute_service[:message]).to include("#{project_user.username}: Access level is not included in the list")
+ expect(project.users).not_to include project_user
+ expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0)
+ end
end
- it 'does not add a member with an existing invite' do
- invited_member = create(:project_member, :invited, project: project)
-
- params = { user_ids: invited_member.invite_email,
- access_level: Gitlab::Access::GUEST }
- result = described_class.new(user, params).execute(project)
+ context 'when passing an existing invite user id' do
+ let(:user_ids) { create(:project_member, :invited, project: project).invite_email }
- expect(result[:status]).to eq(:error)
- expect(result[:message]).to eq('Invite email has already been taken')
+ it 'does not add a member' do
+ expect(execute_service[:status]).to eq(:error)
+ expect(execute_service[:message]).to eq('Invite email has already been taken')
+ expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0)
+ end
end
end
diff --git a/spec/services/members/invitation_reminder_email_service_spec.rb b/spec/services/members/invitation_reminder_email_service_spec.rb
index 88280869476..768a8719d54 100644
--- a/spec/services/members/invitation_reminder_email_service_spec.rb
+++ b/spec/services/members/invitation_reminder_email_service_spec.rb
@@ -9,67 +9,49 @@ RSpec.describe Members::InvitationReminderEmailService do
let_it_be(:frozen_time) { Date.today.beginning_of_day }
let_it_be(:invitation) { build(:group_member, :invited, created_at: frozen_time) }
- context 'when the experiment is disabled' do
- before do
- allow(Gitlab::Experimentation).to receive(:enabled_for_attribute?).and_return(false)
- invitation.expires_at = frozen_time + 2.days
- end
-
- it 'does not send an invitation' do
- travel_to(frozen_time + 1.day) do
- expect(invitation).not_to receive(:send_invitation_reminder)
-
- subject
- end
- end
+ before do
+ invitation.expires_at = frozen_time + expires_at_days.days if expires_at_days
end
- context 'when the experiment is enabled' do
- before do
- allow(Gitlab::Experimentation).to receive(:enabled_for_attribute?).and_return(true)
- invitation.expires_at = frozen_time + expires_at_days.days if expires_at_days
- end
-
- using RSpec::Parameterized::TableSyntax
-
- where(:expires_at_days, :send_reminder_at_days) do
- 0 | []
- 1 | []
- 2 | [1]
- 3 | [1, 2]
- 4 | [1, 2, 3]
- 5 | [1, 2, 4]
- 6 | [1, 3, 5]
- 7 | [1, 3, 5]
- 8 | [2, 3, 6]
- 9 | [2, 4, 7]
- 10 | [2, 4, 8]
- 11 | [2, 4, 8]
- 12 | [2, 5, 9]
- 13 | [2, 5, 10]
- 14 | [2, 5, 10]
- 15 | [2, 5, 10]
- nil | [2, 5, 10]
- end
-
- with_them do
- # Create an invitation today with an expiration date from 0 to 10 days in the future or without an expiration date
- # We chose 10 days here, because we fetch invitations that were created at most 10 days ago.
- (0..10).each do |day|
- it 'sends an invitation reminder only on the expected days' do
- next if day > (expires_at_days || 10) # We don't need to test after the invitation has already expired
-
- # We are traveling in a loop from today to 10 days from now
- travel_to(frozen_time + day.days) do
- # Given an expiration date and the number of days after the creation of the invitation based on the current day in the loop, a reminder may be sent
- if (reminder_index = send_reminder_at_days.index(day))
- expect(invitation).to receive(:send_invitation_reminder).with(reminder_index)
- else
- expect(invitation).not_to receive(:send_invitation_reminder)
- end
+ using RSpec::Parameterized::TableSyntax
+
+ where(:expires_at_days, :send_reminder_at_days) do
+ 0 | []
+ 1 | []
+ 2 | [1]
+ 3 | [1, 2]
+ 4 | [1, 2, 3]
+ 5 | [1, 2, 4]
+ 6 | [1, 3, 5]
+ 7 | [1, 3, 5]
+ 8 | [2, 3, 6]
+ 9 | [2, 4, 7]
+ 10 | [2, 4, 8]
+ 11 | [2, 4, 8]
+ 12 | [2, 5, 9]
+ 13 | [2, 5, 10]
+ 14 | [2, 5, 10]
+ 15 | [2, 5, 10]
+ nil | [2, 5, 10]
+ end
- subject
+ with_them do
+ # Create an invitation today with an expiration date from 0 to 10 days in the future or without an expiration date
+ # We chose 10 days here, because we fetch invitations that were created at most 10 days ago.
+ (0..10).each do |day|
+ it 'sends an invitation reminder only on the expected days' do
+ next if day > (expires_at_days || 10) # We don't need to test after the invitation has already expired
+
+ # We are traveling in a loop from today to 10 days from now
+ travel_to(frozen_time + day.days) do
+ # Given an expiration date and the number of days after the creation of the invitation based on the current day in the loop, a reminder may be sent
+ if (reminder_index = send_reminder_at_days.index(day))
+ expect(invitation).to receive(:send_invitation_reminder).with(reminder_index)
+ else
+ expect(invitation).not_to receive(:send_invitation_reminder)
end
+
+ subject
end
end
end
diff --git a/spec/services/merge_requests/after_create_service_spec.rb b/spec/services/merge_requests/after_create_service_spec.rb
index 840b7bc0a1c..69bab3b1ea4 100644
--- a/spec/services/merge_requests/after_create_service_spec.rb
+++ b/spec/services/merge_requests/after_create_service_spec.rb
@@ -18,32 +18,34 @@ RSpec.describe MergeRequests::AfterCreateService do
allow(after_create_service).to receive(:notification_service).and_return(notification_service)
end
+ subject(:execute_service) { after_create_service.execute(merge_request) }
+
it 'creates a merge request open event' do
expect(event_service)
.to receive(:open_mr).with(merge_request, merge_request.author)
- after_create_service.execute(merge_request)
+ execute_service
end
it 'creates a new merge request notification' do
expect(notification_service)
.to receive(:new_merge_request).with(merge_request, merge_request.author)
- after_create_service.execute(merge_request)
+ execute_service
end
it 'writes diffs to the cache' do
expect(merge_request)
.to receive_message_chain(:diffs, :write_cache)
- after_create_service.execute(merge_request)
+ execute_service
end
it 'creates cross references' do
expect(merge_request)
.to receive(:create_cross_references!).with(merge_request.author)
- after_create_service.execute(merge_request)
+ execute_service
end
it 'creates a pipeline and updates the HEAD pipeline' do
@@ -51,7 +53,14 @@ RSpec.describe MergeRequests::AfterCreateService do
.to receive(:create_pipeline_for).with(merge_request, merge_request.author)
expect(merge_request).to receive(:update_head_pipeline)
- after_create_service.execute(merge_request)
+ execute_service
+ end
+
+ it 'records a namespace onboarding progress action' do
+ expect(NamespaceOnboardingAction).to receive(:create_action)
+ .with(merge_request.target_project.namespace, :merge_request_created).and_call_original
+
+ expect { execute_service }.to change(NamespaceOnboardingAction, :count).by(1)
end
end
end
diff --git a/spec/services/merge_requests/base_service_spec.rb b/spec/services/merge_requests/base_service_spec.rb
index bb7b70f1ba2..83431105545 100644
--- a/spec/services/merge_requests/base_service_spec.rb
+++ b/spec/services/merge_requests/base_service_spec.rb
@@ -20,7 +20,8 @@ RSpec.describe MergeRequests::BaseService do
describe '#execute_hooks' do
shared_examples 'enqueues Jira sync worker' do
- it do
+ specify :aggregate_failures do
+ expect(JiraConnect::SyncMergeRequestWorker).to receive(:perform_async).with(kind_of(Numeric), kind_of(Numeric)).and_call_original
Sidekiq::Testing.fake! do
expect { subject.execute }.to change(JiraConnect::SyncMergeRequestWorker.jobs, :size).by(1)
end
diff --git a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
index 728b343b801..b326fc1726d 100644
--- a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
@@ -146,7 +146,7 @@ RSpec.describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memor
it 'extends dashboard template path to absolute url' do
allow(::Files::CreateService).to receive(:new).and_return(double(execute: { status: :success }))
- expect(File).to receive(:read).with(Rails.root.join('config/prometheus/common_metrics.yml')).and_return('')
+ expect_file_read(Rails.root.join('config/prometheus/common_metrics.yml'), content: '')
service_call
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index f9a89c6281e..9431c023850 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -353,7 +353,7 @@ RSpec.describe NotificationService, :mailer do
context 'a service-desk issue' do
before do
- issue.update!(service_desk_reply_to: 'service.desk@example.com')
+ issue.update!(external_author: 'service.desk@example.com')
project.update!(service_desk_enabled: true)
end
@@ -1549,6 +1549,37 @@ RSpec.describe NotificationService, :mailer do
end
end
+ describe '#issue_cloned' do
+ let(:new_issue) { create(:issue) }
+
+ it 'sends email to issue notification recipients' do
+ notification.issue_cloned(issue, new_issue, @u_disabled)
+
+ should_email(issue.assignees.first)
+ should_email(issue.author)
+ should_email(@u_watcher)
+ should_email(@u_guest_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
+ should_not_email(@u_lazy_participant)
+ end
+
+ it_behaves_like 'participating notifications' do
+ let(:participant) { create(:user, username: 'user-participant') }
+ let(:issuable) { issue }
+ let(:notification_trigger) { notification.issue_cloned(issue, new_issue, @u_disabled) }
+ end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.issue_cloned(issue, new_issue, @u_disabled) }
+ end
+ end
+
describe '#issue_due' do
before do
issue.update!(due_date: Date.today)
@@ -2326,6 +2357,20 @@ RSpec.describe NotificationService, :mailer do
end
end
+ describe '#user_admin_rejection', :deliver_mails_inline do
+ let_it_be(:user) { create(:user, :blocked_pending_approval) }
+
+ before do
+ reset_delivered_emails!
+ end
+
+ it 'sends the user a rejection email' do
+ notification.user_admin_rejection(user.name, user.email)
+
+ should_only_email(user)
+ end
+ end
+
describe 'GroupMember', :deliver_mails_inline do
let(:added_user) { create(:user) }
diff --git a/spec/services/onboarding_progress_service_spec.rb b/spec/services/onboarding_progress_service_spec.rb
new file mode 100644
index 00000000000..59b6083d38a
--- /dev/null
+++ b/spec/services/onboarding_progress_service_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe OnboardingProgressService do
+ describe '#execute' do
+ let(:namespace) { create(:namespace, parent: root_namespace) }
+
+ subject(:execute_service) { described_class.new(namespace).execute(action: :subscription_created) }
+
+ context 'when the namespace is a root' do
+ let(:root_namespace) { nil }
+
+ it 'records a namespace onboarding progress action for the given namespace' do
+ expect(NamespaceOnboardingAction).to receive(:create_action)
+ .with(namespace, :subscription_created).and_call_original
+
+ expect { execute_service }.to change(NamespaceOnboardingAction, :count).by(1)
+ end
+ end
+
+ context 'when the namespace is not the root' do
+ let_it_be(:root_namespace) { build(:namespace) }
+
+ it 'records a namespace onboarding progress action for the root namespace' do
+ expect(NamespaceOnboardingAction).to receive(:create_action)
+ .with(root_namespace, :subscription_created).and_call_original
+
+ expect { execute_service }.to change(NamespaceOnboardingAction, :count).by(1)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/composer/create_package_service_spec.rb b/spec/services/packages/composer/create_package_service_spec.rb
index a1fe9a1b918..d10356cfda7 100644
--- a/spec/services/packages/composer/create_package_service_spec.rb
+++ b/spec/services/packages/composer/create_package_service_spec.rb
@@ -41,6 +41,8 @@ RSpec.describe Packages::Composer::CreatePackageService do
it_behaves_like 'assigns the package creator' do
let(:package) { created_package }
end
+
+ it_behaves_like 'assigns build to package'
end
context 'with a tag' do
@@ -62,6 +64,8 @@ RSpec.describe Packages::Composer::CreatePackageService do
it_behaves_like 'assigns the package creator' do
let(:package) { created_package }
end
+
+ it_behaves_like 'assigns build to package'
end
end
diff --git a/spec/services/packages/conan/create_package_file_service_spec.rb b/spec/services/packages/conan/create_package_file_service_spec.rb
index bd6a91c883a..e655b8d1f9e 100644
--- a/spec/services/packages/conan/create_package_file_service_spec.rb
+++ b/spec/services/packages/conan/create_package_file_service_spec.rb
@@ -5,11 +5,12 @@ RSpec.describe Packages::Conan::CreatePackageFileService do
include WorkhorseHelpers
let_it_be(:package) { create(:conan_package) }
+ let_it_be(:user) { create(:user) }
describe '#execute' do
let(:file_name) { 'foo.tgz' }
- subject { described_class.new(package, file, params) }
+ subject { described_class.new(package, file, params).execute }
shared_examples 'a valid package_file' do
let(:params) do
@@ -27,7 +28,7 @@ RSpec.describe Packages::Conan::CreatePackageFileService do
end
it 'creates a new package file' do
- package_file = subject.execute
+ package_file = subject
expect(package_file).to be_valid
expect(package_file.file_name).to eq(file_name)
@@ -40,6 +41,8 @@ RSpec.describe Packages::Conan::CreatePackageFileService do
expect(package_file.conan_file_metadatum.conan_file_type).to eq('package_file')
expect(package_file.file.read).to eq('content')
end
+
+ it_behaves_like 'assigns build to package file'
end
shared_examples 'a valid recipe_file' do
@@ -56,7 +59,7 @@ RSpec.describe Packages::Conan::CreatePackageFileService do
end
it 'creates a new recipe file' do
- package_file = subject.execute
+ package_file = subject
expect(package_file).to be_valid
expect(package_file.file_name).to eq(file_name)
@@ -69,6 +72,8 @@ RSpec.describe Packages::Conan::CreatePackageFileService do
expect(package_file.conan_file_metadatum.conan_file_type).to eq('recipe_file')
expect(package_file.file.read).to eq('content')
end
+
+ it_behaves_like 'assigns build to package file'
end
context 'with temp file' do
@@ -123,7 +128,7 @@ RSpec.describe Packages::Conan::CreatePackageFileService do
end
it 'raises an error' do
- expect { subject.execute }.to raise_error(ActiveRecord::RecordInvalid)
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
end
end
end
diff --git a/spec/services/packages/conan/create_package_service_spec.rb b/spec/services/packages/conan/create_package_service_spec.rb
index b217e570aba..ca783475503 100644
--- a/spec/services/packages/conan/create_package_service_spec.rb
+++ b/spec/services/packages/conan/create_package_service_spec.rb
@@ -30,6 +30,7 @@ RSpec.describe Packages::Conan::CreatePackageService do
end
it_behaves_like 'assigns the package creator'
+ it_behaves_like 'assigns build to package'
end
context 'invalid params' do
diff --git a/spec/services/packages/create_event_service_spec.rb b/spec/services/packages/create_event_service_spec.rb
index 4db7687bb24..f581d704087 100644
--- a/spec/services/packages/create_event_service_spec.rb
+++ b/spec/services/packages/create_event_service_spec.rb
@@ -70,12 +70,34 @@ RSpec.describe Packages::CreateEventService do
end
it 'tracks the event' do
+ expect(::Gitlab::UsageDataCounters::GuestPackageEventCounter).not_to receive(:count)
expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(user.id, Packages::Event.allowed_event_name(expected_scope, event_name, originator_type))
subject
end
end
+ shared_examples 'redis package guest event creation' do |originator_type, expected_scope|
+ context 'with feature flag disabled' do
+ before do
+ stub_feature_flags(collect_package_events_redis: false)
+ end
+
+ it 'does not track the event' do
+ expect(::Gitlab::UsageDataCounters::GuestPackageEventCounter).not_to receive(:count)
+
+ subject
+ end
+ end
+
+ it 'tracks the event' do
+ expect(::Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+ expect(::Gitlab::UsageDataCounters::GuestPackageEventCounter).to receive(:count).with(Packages::Event.allowed_event_name(expected_scope, event_name, originator_type))
+
+ subject
+ end
+ end
+
context 'with a user' do
let(:user) { create(:user) }
@@ -94,6 +116,7 @@ RSpec.describe Packages::CreateEventService do
let(:user) { nil }
it_behaves_like 'db package event creation', 'guest', 'container'
+ it_behaves_like 'redis package guest event creation', 'guest', 'container'
end
context 'with a package as scope' do
@@ -103,6 +126,7 @@ RSpec.describe Packages::CreateEventService do
let(:user) { nil }
it_behaves_like 'db package event creation', 'guest', 'npm'
+ it_behaves_like 'redis package guest event creation', 'guest', 'npm'
end
context 'with user' do
diff --git a/spec/services/packages/create_package_file_service_spec.rb b/spec/services/packages/create_package_file_service_spec.rb
index 12fd1039d30..e4b4b15ebf9 100644
--- a/spec/services/packages/create_package_file_service_spec.rb
+++ b/spec/services/packages/create_package_file_service_spec.rb
@@ -4,10 +4,11 @@ require 'spec_helper'
RSpec.describe Packages::CreatePackageFileService do
let_it_be(:package) { create(:maven_package) }
let_it_be(:user) { create(:user) }
-
- subject { described_class.new(package, params) }
+ let(:service) { described_class.new(package, params) }
describe '#execute' do
+ subject { service.execute }
+
context 'with valid params' do
let(:params) do
{
@@ -17,11 +18,13 @@ RSpec.describe Packages::CreatePackageFileService do
end
it 'creates a new package file' do
- package_file = subject.execute
+ package_file = subject
expect(package_file).to be_valid
expect(package_file.file_name).to eq('foo.jar')
end
+
+ it_behaves_like 'assigns build to package file'
end
context 'file is missing' do
@@ -32,17 +35,7 @@ RSpec.describe Packages::CreatePackageFileService do
end
it 'raises an error' do
- expect { subject.execute }.to raise_error(ActiveRecord::RecordInvalid)
- end
- end
-
- context 'with a build' do
- let_it_be(:pipeline) { create(:ci_pipeline, user: user) }
- let(:build) { double('build', pipeline: pipeline) }
- let(:params) { { file: Tempfile.new, file_name: 'foo.jar', build: build } }
-
- it 'creates a build_info' do
- expect { subject.execute }.to change { Packages::PackageFileBuildInfo.count }.by(1)
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
end
end
end
diff --git a/spec/services/packages/generic/create_package_file_service_spec.rb b/spec/services/packages/generic/create_package_file_service_spec.rb
index 907483e3d7f..816e728c342 100644
--- a/spec/services/packages/generic/create_package_file_service_spec.rb
+++ b/spec/services/packages/generic/create_package_file_service_spec.rb
@@ -23,6 +23,8 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
}
end
+ subject { described_class.new(project, user, params).execute }
+
before do
FileUtils.touch(temp_file)
end
@@ -41,9 +43,7 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
expect(::Packages::Generic::FindOrCreatePackageService).to receive(:new).with(project, user, package_params).and_return(package_service)
expect(package_service).to receive(:execute).and_return(package)
- service = described_class.new(project, user, params)
-
- expect { service.execute }.to change { package.package_files.count }.by(1)
+ expect { subject }.to change { package.package_files.count }.by(1)
.and change { Packages::PackageFileBuildInfo.count }.by(1)
package_file = package.package_files.last
@@ -54,5 +54,7 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
expect(package_file.file_sha256).to eq(sha256)
end
end
+
+ it_behaves_like 'assigns build to package file'
end
end
diff --git a/spec/services/packages/nuget/create_package_service_spec.rb b/spec/services/packages/nuget/create_package_service_spec.rb
index e51bc03f311..5289ad40d61 100644
--- a/spec/services/packages/nuget/create_package_service_spec.rb
+++ b/spec/services/packages/nuget/create_package_service_spec.rb
@@ -31,5 +31,6 @@ RSpec.describe Packages::Nuget::CreatePackageService do
end
it_behaves_like 'assigns the package creator'
+ it_behaves_like 'assigns build to package'
end
end
diff --git a/spec/services/packages/pypi/create_package_service_spec.rb b/spec/services/packages/pypi/create_package_service_spec.rb
index c985c1e54ea..28a727c4a09 100644
--- a/spec/services/packages/pypi/create_package_service_spec.rb
+++ b/spec/services/packages/pypi/create_package_service_spec.rb
@@ -51,6 +51,8 @@ RSpec.describe Packages::Pypi::CreatePackageService do
let(:package) { created_package }
end
+ it_behaves_like 'assigns build to package'
+
context 'with an existing package' do
before do
described_class.new(project, user, params).execute
diff --git a/spec/services/pages/legacy_storage_lease_spec.rb b/spec/services/pages/legacy_storage_lease_spec.rb
new file mode 100644
index 00000000000..c022da6f47f
--- /dev/null
+++ b/spec/services/pages/legacy_storage_lease_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Pages::LegacyStorageLease do
+ let(:project) { create(:project) }
+
+ let(:implementation) do
+ Class.new do
+ include ::Pages::LegacyStorageLease
+
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute
+ try_obtain_lease do
+ execute_unsafe
+ end
+ end
+
+ def execute_unsafe
+ true
+ end
+ end
+ end
+
+ let(:service) { implementation.new(project) }
+
+ it 'allows method to be executed' do
+ expect(service).to receive(:execute_unsafe).and_call_original
+
+ expect(service.execute).to eq(true)
+ end
+
+ context 'when another service holds the lease for the same project' do
+ around do |example|
+ implementation.new(project).try_obtain_lease do
+ example.run
+ end
+ end
+
+ it 'does not run guarded method' do
+ expect(service).not_to receive(:execute_unsafe)
+
+ expect(service.execute).to eq(nil)
+ end
+
+ it 'runs guarded method if feature flag is disabled' do
+ stub_feature_flags(pages_use_legacy_storage_lease: false)
+
+ expect(service).to receive(:execute_unsafe).and_call_original
+
+ expect(service.execute).to eq(true)
+ end
+ end
+
+ context 'when another service holds the lease for the different project' do
+ around do |example|
+ implementation.new(create(:project)).try_obtain_lease do
+ example.run
+ end
+ end
+
+ it 'allows method to be executed' do
+ expect(service).to receive(:execute_unsafe).and_call_original
+
+ expect(service.execute).to eq(true)
+ end
+ end
+end
diff --git a/spec/services/pages/zip_directory_service_spec.rb b/spec/services/pages/zip_directory_service_spec.rb
new file mode 100644
index 00000000000..1568103d102
--- /dev/null
+++ b/spec/services/pages/zip_directory_service_spec.rb
@@ -0,0 +1,209 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Pages::ZipDirectoryService do
+ around do |example|
+ Dir.mktmpdir do |dir|
+ @work_dir = dir
+ example.run
+ end
+ end
+
+ let(:result) do
+ described_class.new(@work_dir).execute
+ end
+
+ let(:archive) { result.first }
+ let(:entries_count) { result.second }
+
+ it 'raises error if there is no public directory' do
+ expect { archive }.to raise_error(described_class::InvalidArchiveError)
+ end
+
+ it 'raises error if public directory is a symlink' do
+ create_dir('target')
+ create_file('./target/index.html', 'hello')
+ create_link("public", "./target")
+
+ expect { archive }.to raise_error(described_class::InvalidArchiveError)
+ end
+
+ context 'when there is a public directory' do
+ before do
+ create_dir('public')
+ end
+
+ it 'creates the file next the public directory' do
+ expect(archive).to eq(File.join(@work_dir, "@migrated.zip"))
+ end
+
+ it 'includes public directory' do
+ with_zip_file do |zip_file|
+ entry = zip_file.get_entry("public/")
+ expect(entry.ftype).to eq(:directory)
+ end
+ end
+
+ it 'returns number of entries' do
+ create_file("public/index.html", "hello")
+ create_link("public/link.html", "./index.html")
+ expect(entries_count).to eq(3) # + 'public' directory
+ end
+
+ it 'removes the old file if it exists' do
+ # simulate the old run
+ described_class.new(@work_dir).execute
+
+ with_zip_file do |zip_file|
+ expect(zip_file.entries.count).to eq(1)
+ end
+ end
+
+ it 'ignores other top level files and directories' do
+ create_file("top_level.html", "hello")
+ create_dir("public2")
+
+ with_zip_file do |zip_file|
+ expect { zip_file.get_entry("top_level.html") }.to raise_error(Errno::ENOENT)
+ expect { zip_file.get_entry("public2/") }.to raise_error(Errno::ENOENT)
+ end
+ end
+
+ it 'includes index.html file' do
+ create_file("public/index.html", "Hello!")
+
+ with_zip_file do |zip_file|
+ entry = zip_file.get_entry("public/index.html")
+ expect(zip_file.read(entry)).to eq("Hello!")
+ end
+ end
+
+ it 'includes hidden file' do
+ create_file("public/.hidden.html", "Hello!")
+
+ with_zip_file do |zip_file|
+ entry = zip_file.get_entry("public/.hidden.html")
+ expect(zip_file.read(entry)).to eq("Hello!")
+ end
+ end
+
+ it 'includes nested directories and files' do
+ create_dir("public/nested")
+ create_dir("public/nested/nested2")
+ create_file("public/nested/nested2/nested.html", "Hello nested")
+
+ with_zip_file do |zip_file|
+ entry = zip_file.get_entry("public/nested")
+ expect(entry.ftype).to eq(:directory)
+
+ entry = zip_file.get_entry("public/nested/nested2")
+ expect(entry.ftype).to eq(:directory)
+
+ entry = zip_file.get_entry("public/nested/nested2/nested.html")
+ expect(zip_file.read(entry)).to eq("Hello nested")
+ end
+ end
+
+ it 'adds a valid symlink' do
+ create_file("public/target.html", "hello")
+ create_link("public/link.html", "./target.html")
+
+ with_zip_file do |zip_file|
+ entry = zip_file.get_entry("public/link.html")
+ expect(entry.ftype).to eq(:symlink)
+ expect(zip_file.read(entry)).to eq("./target.html")
+ end
+ end
+
+ it 'ignores the symlink pointing outside of public directory' do
+ create_file("target.html", "hello")
+ create_link("public/link.html", "../target.html")
+
+ with_zip_file do |zip_file|
+ expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
+ end
+ end
+
+ it 'ignores the symlink if target is absent' do
+ create_link("public/link.html", "./target.html")
+
+ with_zip_file do |zip_file|
+ expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
+ end
+ end
+
+ it 'ignores symlink if is absolute and points to outside of directory' do
+ target = File.join(@work_dir, "target")
+ FileUtils.touch(target)
+
+ create_link("public/link.html", target)
+
+ with_zip_file do |zip_file|
+ expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
+ end
+ end
+
+ it "includes raw symlink if it's target is a valid directory" do
+ create_dir("public/target")
+ create_file("public/target/index.html", "hello")
+ create_link("public/link", "./target")
+
+ with_zip_file do |zip_file|
+ expect(zip_file.entries.count).to eq(4) # /public and 3 created above
+
+ entry = zip_file.get_entry("public/link")
+ expect(entry.ftype).to eq(:symlink)
+ expect(zip_file.read(entry)).to eq("./target")
+ end
+ end
+ end
+
+ context "validating fixtures pages archives" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:fixture_path) do
+ ["spec/fixtures/pages.zip", "spec/fixtures/pages_non_writeable.zip"]
+ end
+
+ with_them do
+ let(:full_fixture_path) { Rails.root.join(fixture_path) }
+
+ it 'a created archives contains exactly the same entries' do
+ SafeZip::Extract.new(full_fixture_path).extract(directories: ['public'], to: @work_dir)
+
+ with_zip_file do |created_archive|
+ Zip::File.open(full_fixture_path) do |original_archive|
+ original_archive.entries do |original_entry|
+ created_entry = created_archive.get_entry(original_entry.name)
+
+ expect(created_entry.name).to eq(original_entry.name)
+ expect(created_entry.ftype).to eq(original_entry.ftype)
+ expect(created_archive.read(created_entry)).to eq(original_archive.read(original_entry))
+ end
+ end
+ end
+ end
+ end
+ end
+
+ def create_file(name, content)
+ File.open(File.join(@work_dir, name), "w") do |f|
+ f.write(content)
+ end
+ end
+
+ def create_dir(dir)
+ Dir.mkdir(File.join(@work_dir, dir))
+ end
+
+ def create_link(new_name, target)
+ File.symlink(target, File.join(@work_dir, new_name))
+ end
+
+ def with_zip_file
+ Zip::File.open(archive) do |zip_file|
+ yield zip_file
+ end
+ end
+end
diff --git a/spec/services/post_receive_service_spec.rb b/spec/services/post_receive_service_spec.rb
index 7c4b7f51cc3..4e303bfc20a 100644
--- a/spec/services/post_receive_service_spec.rb
+++ b/spec/services/post_receive_service_spec.rb
@@ -45,6 +45,12 @@ RSpec.describe PostReceiveService do
it 'does not return error' do
expect(subject).to be_empty
end
+
+ it 'does not record a namespace onboarding progress action' do
+ expect(NamespaceOnboardingAction).not_to receive(:create_action)
+
+ subject
+ end
end
context 'when repository is nil' do
@@ -80,6 +86,13 @@ RSpec.describe PostReceiveService do
expect(response.reference_counter_decreased).to be(true)
end
+
+ it 'records a namespace onboarding progress action' do
+ expect(NamespaceOnboardingAction).to receive(:create_action)
+ .with(project.namespace, :git_write)
+
+ subject
+ end
end
context 'with Project' do
diff --git a/spec/services/projects/alerting/notify_service_spec.rb b/spec/services/projects/alerting/notify_service_spec.rb
index 4674f614cf1..4b7b7b0b200 100644
--- a/spec/services/projects/alerting/notify_service_spec.rb
+++ b/spec/services/projects/alerting/notify_service_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Projects::Alerting::NotifyService do
let(:token) { 'invalid-token' }
let(:starts_at) { Time.current.change(usec: 0) }
let(:fingerprint) { 'testing' }
- let(:service) { described_class.new(project, nil, payload) }
+ let(:service) { described_class.new(project, payload) }
let_it_be(:environment) { create(:environment, project: project) }
let(:environment) { create(:environment, project: project) }
let(:ended_at) { nil }
@@ -54,7 +54,6 @@ RSpec.describe Projects::Alerting::NotifyService do
shared_examples 'assigns the alert properties' do
it 'ensure that created alert has all data properly assigned' do
subject
-
expect(last_alert_attributes).to match(
project_id: project.id,
title: payload_raw.fetch(:title),
@@ -62,6 +61,7 @@ RSpec.describe Projects::Alerting::NotifyService do
severity: payload_raw.fetch(:severity),
status: AlertManagement::Alert.status_value(:triggered),
events: 1,
+ domain: 'operations',
hosts: payload_raw.fetch(:hosts),
payload: payload_raw.with_indifferent_access,
issue_id: nil,
@@ -187,6 +187,7 @@ RSpec.describe Projects::Alerting::NotifyService do
status: AlertManagement::Alert.status_value(:triggered),
events: 1,
hosts: [],
+ domain: 'operations',
payload: payload_raw.with_indifferent_access,
issue_id: nil,
description: nil,
diff --git a/spec/services/projects/container_repository/delete_tags_service_spec.rb b/spec/services/projects/container_repository/delete_tags_service_spec.rb
index a012ec29be5..4da416d9698 100644
--- a/spec/services/projects/container_repository/delete_tags_service_spec.rb
+++ b/spec/services/projects/container_repository/delete_tags_service_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do
service_class: 'Projects::ContainerRepository::DeleteTagsService',
message: 'deleted tags',
container_repository_id: repository.id,
+ project_id: repository.project_id,
deleted_tags_count: tags.size
)
@@ -32,7 +33,8 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do
log_data = {
service_class: 'Projects::ContainerRepository::DeleteTagsService',
message: message,
- container_repository_id: repository.id
+ container_repository_id: repository.id,
+ project_id: repository.project_id
}
log_data.merge!(extra_log) if extra_log.any?
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 555f2f5a5e5..60dfee820ca 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -325,7 +325,7 @@ RSpec.describe Projects::ForkService do
storage_move = create(
:project_repository_storage_move,
:scheduled,
- project: project,
+ container: project,
destination_storage_name: 'test_second_storage'
)
Projects::UpdateRepositoryStorageService.new(storage_move).execute
@@ -344,10 +344,6 @@ RSpec.describe Projects::ForkService do
let(:fork_from_project) { create(:project, :public) }
let(:forker) { create(:user) }
- before do
- stub_feature_flags(object_pools: true)
- end
-
context 'when no pool exists' do
it 'creates a new object pool' do
forked_project = fork_project(fork_from_project, forker, using_service: true)
diff --git a/spec/services/projects/git_deduplication_service_spec.rb b/spec/services/projects/git_deduplication_service_spec.rb
index b98db5bc41b..e6eff936de7 100644
--- a/spec/services/projects/git_deduplication_service_spec.rb
+++ b/spec/services/projects/git_deduplication_service_spec.rb
@@ -139,7 +139,7 @@ RSpec.describe Projects::GitDeduplicationService do
end
it 'fails when a lease is already out' do
- expect(service).to receive(:log_error).with("Cannot obtain an exclusive lease for #{service.class.name}. There must be another instance already in execution.")
+ expect(service).to receive(:log_error).with("Cannot obtain an exclusive lease for #{lease_key}. There must be another instance already in execution.")
service.execute
end
diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
index f0fd243f0ca..47252bcf7a7 100644
--- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Projects::HashedStorage::MigrateRepositoryService do
end
it 'fails when a git operation is in progress' do
- allow(project).to receive(:repo_reference_count) { 1 }
+ allow(project).to receive(:git_transfer_in_progress?) { true }
expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryInUseError)
end
diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
index 492eb0956aa..af128a532b9 100644
--- a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab
end
it 'fails when a git operation is in progress' do
- allow(project).to receive(:repo_reference_count) { 1 }
+ allow(project).to receive(:git_transfer_in_progress?) { true }
expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryInUseError)
end
diff --git a/spec/services/projects/move_access_service_spec.rb b/spec/services/projects/move_access_service_spec.rb
index 02f80988dd1..90167ffebed 100644
--- a/spec/services/projects/move_access_service_spec.rb
+++ b/spec/services/projects/move_access_service_spec.rb
@@ -91,7 +91,7 @@ RSpec.describe Projects::MoveAccessService do
it 'does not remove remaining memberships' do
target_project.add_maintainer(maintainer_user)
- subject.execute(project_with_access, options)
+ subject.execute(project_with_access, **options)
expect(project_with_access.project_members.count).not_to eq 0
end
@@ -99,7 +99,7 @@ RSpec.describe Projects::MoveAccessService do
it 'does not remove remaining group links' do
target_project.project_group_links.create!(group: maintainer_group, group_access: Gitlab::Access::MAINTAINER)
- subject.execute(project_with_access, options)
+ subject.execute(project_with_access, **options)
expect(project_with_access.project_group_links.count).not_to eq 0
end
@@ -107,7 +107,7 @@ RSpec.describe Projects::MoveAccessService do
it 'does not remove remaining authorizations' do
target_project.add_developer(developer_user)
- subject.execute(project_with_access, options)
+ subject.execute(project_with_access, **options)
expect(project_with_access.project_authorizations.count).not_to eq 0
end
diff --git a/spec/services/projects/move_deploy_keys_projects_service_spec.rb b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
index e69b4dd4fc7..bd93b80f712 100644
--- a/spec/services/projects/move_deploy_keys_projects_service_spec.rb
+++ b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe Projects::MoveDeployKeysProjectsService do
it 'does not remove remaining deploy keys projects' do
target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first
- subject.execute(project_with_deploy_keys, options)
+ subject.execute(project_with_deploy_keys, **options)
expect(project_with_deploy_keys.deploy_keys_projects.count).not_to eq 0
end
diff --git a/spec/services/projects/move_lfs_objects_projects_service_spec.rb b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
index b73286fba9a..e3df5fed9cf 100644
--- a/spec/services/projects/move_lfs_objects_projects_service_spec.rb
+++ b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
@@ -48,7 +48,7 @@ RSpec.describe Projects::MoveLfsObjectsProjectsService do
it 'does not remove remaining lfs objects' do
target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2)
- subject.execute(project_with_lfs_objects, options)
+ subject.execute(project_with_lfs_objects, **options)
expect(project_with_lfs_objects.lfs_objects.count).not_to eq 0
end
diff --git a/spec/services/projects/move_notification_settings_service_spec.rb b/spec/services/projects/move_notification_settings_service_spec.rb
index 7c9f1dd30d2..e381ae7590f 100644
--- a/spec/services/projects/move_notification_settings_service_spec.rb
+++ b/spec/services/projects/move_notification_settings_service_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe Projects::MoveNotificationSettingsService do
let(:options) { { remove_remaining_elements: false } }
it 'does not remove remaining notification settings' do
- subject.execute(project_with_notifications, options)
+ subject.execute(project_with_notifications, **options)
expect(project_with_notifications.notification_settings.count).not_to eq 0
end
diff --git a/spec/services/projects/move_project_authorizations_service_spec.rb b/spec/services/projects/move_project_authorizations_service_spec.rb
index a37b4d807a0..d47b13ca939 100644
--- a/spec/services/projects/move_project_authorizations_service_spec.rb
+++ b/spec/services/projects/move_project_authorizations_service_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe Projects::MoveProjectAuthorizationsService do
target_project.add_maintainer(developer_user)
target_project.add_developer(reporter_user)
- subject.execute(project_with_users, options)
+ subject.execute(project_with_users, **options)
expect(project_with_users.project_authorizations.count).not_to eq 0
end
diff --git a/spec/services/projects/move_project_group_links_service_spec.rb b/spec/services/projects/move_project_group_links_service_spec.rb
index 6304eded8d3..1fca96a0367 100644
--- a/spec/services/projects/move_project_group_links_service_spec.rb
+++ b/spec/services/projects/move_project_group_links_service_spec.rb
@@ -58,7 +58,7 @@ RSpec.describe Projects::MoveProjectGroupLinksService do
target_project.project_group_links.create!(group: maintainer_group, group_access: Gitlab::Access::MAINTAINER)
target_project.project_group_links.create!(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
- subject.execute(project_with_groups, options)
+ subject.execute(project_with_groups, **options)
expect(project_with_groups.project_group_links.count).not_to eq 0
end
diff --git a/spec/services/projects/move_project_members_service_spec.rb b/spec/services/projects/move_project_members_service_spec.rb
index f14f00e3866..8fbd0ba3270 100644
--- a/spec/services/projects/move_project_members_service_spec.rb
+++ b/spec/services/projects/move_project_members_service_spec.rb
@@ -58,7 +58,7 @@ RSpec.describe Projects::MoveProjectMembersService do
target_project.add_maintainer(developer_user)
target_project.add_developer(reporter_user)
- subject.execute(project_with_users, options)
+ subject.execute(project_with_users, **options)
expect(project_with_users.project_members.count).not_to eq 0
end
diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb
index 294c9adcc92..c739fea5ecf 100644
--- a/spec/services/projects/open_issues_count_service_spec.rb
+++ b/spec/services/projects/open_issues_count_service_spec.rb
@@ -10,14 +10,6 @@ RSpec.describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_c
it_behaves_like 'a counter caching service'
describe '#count' do
- it 'does not count test cases' do
- create(:issue, :opened, project: project)
- create(:incident, :opened, project: project)
- create(:quality_test_case, :opened, project: project)
-
- expect(described_class.new(project).count).to eq(2)
- end
-
context 'when user is nil' do
it 'does not include confidential issues in the issue count' do
create(:issue, :opened, project: project)
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
index 33a3e37a2d2..b84e28314f2 100644
--- a/spec/services/projects/participants_service_spec.rb
+++ b/spec/services/projects/participants_service_spec.rb
@@ -3,57 +3,100 @@
require 'spec_helper'
RSpec.describe Projects::ParticipantsService do
- describe '#groups' do
+ describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
- let(:service) { described_class.new(project, user) }
+ let_it_be(:noteable) { create(:issue, project: project) }
- it 'avoids N+1 queries' do
- group_1 = create(:group)
- group_1.add_owner(user)
+ before_all do
+ project.add_developer(user)
+ end
- service.groups # Run general application warmup queries
- control_count = ActiveRecord::QueryRecorder.new { service.groups }.count
+ def run_service
+ described_class.new(project, user).execute(noteable)
+ end
- group_2 = create(:group)
- group_2.add_owner(user)
+ context 'N+1 checks' do
+ before do
+ run_service # warmup, runs table cache queries and create queries
+ BatchLoader::Executor.clear_current
+ end
- expect { service.groups }.not_to exceed_query_limit(control_count)
- end
+ it 'avoids N+1 UserDetail queries' do
+ project.add_developer(create(:user))
- it 'returns correct user counts for groups' do
- group_1 = create(:group)
- group_1.add_owner(user)
- group_1.add_owner(create(:user))
+ control_count = ActiveRecord::QueryRecorder.new { run_service.to_a }.count
- group_2 = create(:group)
- group_2.add_owner(user)
- create(:group_member, :access_request, group: group_2, user: create(:user))
+ BatchLoader::Executor.clear_current
- expect(service.groups).to contain_exactly(
- a_hash_including(name: group_1.full_name, count: 2),
- a_hash_including(name: group_2.full_name, count: 1)
- )
- end
+ project.add_developer(create(:user, status: build(:user_status, availability: :busy)))
+
+ expect { run_service.to_a }.not_to exceed_query_limit(control_count)
+ end
- describe 'avatar_url' do
- let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
+ it 'avoids N+1 groups queries' do
+ group_1 = create(:group)
+ group_1.add_owner(user)
- before do
- group.add_owner(user)
+ control_count = ActiveRecord::QueryRecorder.new { run_service }.count
+
+ BatchLoader::Executor.clear_current
+
+ group_2 = create(:group)
+ group_2.add_owner(user)
+
+ expect { run_service }.not_to exceed_query_limit(control_count)
end
+ end
+
+ it 'does not return duplicate author' do
+ participants = run_service
- it 'returns an url for the avatar' do
- expect(service.groups.size).to eq 1
- expect(service.groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png")
+ expect(participants.count { |p| p[:username] == noteable.author.username }).to eq 1
+ end
+
+ describe 'group items' do
+ subject(:group_items) { run_service.select { |hash| hash[:type].eql?('Group') } }
+
+ describe 'group user counts' do
+ let(:group_1) { create(:group) }
+ let(:group_2) { create(:group) }
+
+ before do
+ group_1.add_owner(user)
+ group_1.add_owner(create(:user))
+
+ group_2.add_owner(user)
+ create(:group_member, :access_request, group: group_2, user: create(:user))
+ end
+
+ it 'returns correct user counts for groups' do
+ expect(group_items).to contain_exactly(
+ a_hash_including(name: group_1.full_name, count: 2),
+ a_hash_including(name: group_2.full_name, count: 1)
+ )
+ end
end
- it 'returns an url for the avatar with relative url' do
- stub_config_setting(relative_url_root: '/gitlab')
- stub_config_setting(url: Settings.send(:build_gitlab_url))
+ describe 'avatar_url' do
+ let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
+
+ before do
+ group.add_owner(user)
+ end
- expect(service.groups.size).to eq 1
- expect(service.groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png")
+ it 'returns an url for the avatar' do
+ expect(group_items.size).to eq 1
+ expect(group_items.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png")
+ end
+
+ it 'returns an url for the avatar with relative url' do
+ stub_config_setting(relative_url_root: '/gitlab')
+ stub_config_setting(url: Settings.send(:build_gitlab_url))
+
+ expect(group_items.size).to eq 1
+ expect(group_items.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png")
+ end
end
end
end
diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
index 0e5ac7c69e3..8ae47ec266c 100644
--- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
let_it_be(:project, reload: true) { create(:project) }
- let(:service) { described_class.new(project, nil, payload) }
+ let(:service) { described_class.new(project, payload) }
let(:token_input) { 'token' }
let!(:setting) do
@@ -138,10 +138,10 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
end
end
- context 'with generic alerts integration' do
+ context 'with HTTP integration' do
using RSpec::Parameterized::TableSyntax
- where(:alerts_service, :token, :result) do
+ where(:active, :token, :result) do
:active | :valid | :success
:active | :invalid | :failure
:active | nil | :failure
@@ -150,15 +150,12 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
end
with_them do
- let(:valid) { project.alerts_service.token }
+ let(:valid) { integration.token }
let(:invalid) { 'invalid token' }
let(:token_input) { public_send(token) if token }
+ let(:integration) { create(:alert_management_http_integration, active, project: project) if active }
- before do
- if alerts_service
- create(:alerts_service, alerts_service, project: project)
- end
- end
+ let(:subject) { service.execute(token_input, integration) }
case result = params[:result]
when :success
@@ -221,7 +218,7 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
it 'processes Prometheus alerts' do
expect(AlertManagement::ProcessPrometheusAlertService)
.to receive(:new)
- .with(project, nil, kind_of(Hash))
+ .with(project, kind_of(Hash))
.exactly(3).times
.and_return(process_service)
expect(process_service).to receive(:execute).exactly(3).times
diff --git a/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb b/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb
new file mode 100644
index 00000000000..5b76386bfab
--- /dev/null
+++ b/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::ScheduleBulkRepositoryShardMovesService do
+ before do
+ stub_storage_settings('test_second_storage' => { 'path' => 'tmp/tests/extra_storage' })
+ end
+
+ let!(:project) { create(:project, :repository).tap { |project| project.track_project_repository } }
+ let(:source_storage_name) { 'default' }
+ let(:destination_storage_name) { 'test_second_storage' }
+
+ describe '#execute' do
+ it 'schedules project repository storage moves' do
+ expect { subject.execute(source_storage_name, destination_storage_name) }
+ .to change(ProjectRepositoryStorageMove, :count).by(1)
+
+ storage_move = project.repository_storage_moves.last!
+
+ expect(storage_move).to have_attributes(
+ source_storage_name: source_storage_name,
+ destination_storage_name: destination_storage_name,
+ state_name: :scheduled
+ )
+ end
+
+ context 'read-only repository' do
+ let!(:project) { create(:project, :repository, :read_only).tap { |project| project.track_project_repository } }
+
+ it 'does not get scheduled' do
+ expect(subject).to receive(:log_info)
+ .with("Project #{project.full_path} (#{project.id}) was skipped: Project is read only")
+ expect { subject.execute(source_storage_name, destination_storage_name) }
+ .to change(ProjectRepositoryStorageMove, :count).by(0)
+ end
+ end
+ end
+
+ describe '.enqueue' do
+ it 'defers to the worker' do
+ expect(::ProjectScheduleBulkRepositoryShardMovesWorker).to receive(:perform_async).with(source_storage_name, destination_storage_name)
+
+ described_class.enqueue(source_storage_name, destination_storage_name)
+ end
+ end
+end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 8e6147e7a3c..5f41ec1d610 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Projects::TransferService do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
+ let_it_be(:group_integration) { create(:slack_service, group: group, project: nil, webhook: 'http://group.slack.com') }
let(:project) { create(:project, :repository, :legacy_storage, namespace: user.namespace) }
subject(:execute_transfer) { described_class.new(project, user).execute(group).tap { project.reload } }
@@ -117,6 +118,30 @@ RSpec.describe Projects::TransferService do
shard_name: project.repository_storage
)
end
+
+ context 'with a project integration' do
+ let_it_be_with_reload(:project) { create(:project, namespace: user.namespace) }
+ let_it_be(:instance_integration) { create(:slack_service, :instance, webhook: 'http://project.slack.com') }
+
+ context 'with an inherited integration' do
+ let_it_be(:project_integration) { create(:slack_service, project: project, webhook: 'http://project.slack.com', inherit_from_id: instance_integration.id) }
+
+ it 'replaces inherited integrations', :aggregate_failures do
+ execute_transfer
+
+ expect(project.slack_service.webhook).to eq(group_integration.webhook)
+ expect(Service.count).to eq(3)
+ end
+ end
+
+ context 'with a custom integration' do
+ let_it_be(:project_integration) { create(:slack_service, project: project, webhook: 'http://project.slack.com') }
+
+ it 'does not updates the integrations' do
+ expect { execute_transfer }.not_to change { project.slack_service.webhook }
+ end
+ end
+ end
end
context 'when transfer fails' do
@@ -527,7 +552,7 @@ RSpec.describe Projects::TransferService do
group.add_owner(user)
end
- it 'schedules a job when pages are deployed' do
+ it 'schedules a job when pages are deployed' do
project.mark_pages_as_deployed
expect(PagesTransferWorker).to receive(:perform_async)
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index d2c6c0eb971..a15f6bdbe2c 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -35,13 +35,11 @@ RSpec.describe Projects::UpdatePagesService do
build.reload
end
- describe 'pages artifacts' do
- it "doesn't delete artifacts after deploying" do
- expect(execute).to eq(:success)
+ it "doesn't delete artifacts after deploying" do
+ expect(execute).to eq(:success)
- expect(project.pages_metadatum).to be_deployed
- expect(build.artifacts?).to eq(true)
- end
+ expect(project.pages_metadatum).to be_deployed
+ expect(build.artifacts?).to eq(true)
end
it 'succeeds' do
@@ -71,6 +69,16 @@ RSpec.describe Projects::UpdatePagesService do
expect(project.pages_metadatum.reload.pages_deployment_id).to eq(deployment.id)
end
+ it 'fails if another deployment is in progress' do
+ subject.try_obtain_lease do
+ expect do
+ execute
+ end.to raise_error("Failed to deploy pages - other deployment is in progress")
+
+ expect(GenericCommitStatus.last.description).to eq("Failed to deploy pages - other deployment is in progress")
+ end
+ end
+
it 'does not fail if pages_metadata is absent' do
project.pages_metadatum.destroy!
project.reload
@@ -105,16 +113,6 @@ RSpec.describe Projects::UpdatePagesService do
end
end
- it 'does not create deployment when zip_pages_deployments feature flag is disabled' do
- stub_feature_flags(zip_pages_deployments: false)
-
- expect do
- expect(execute).to eq(:success)
- end.not_to change { project.pages_deployments.count }
-
- expect(project.pages_metadatum.reload.pages_deployment_id).to be_nil
- end
-
it 'limits pages size' do
stub_application_setting(max_pages_size: 1)
expect(execute).not_to eq(:success)
diff --git a/spec/services/projects/update_repository_storage_service_spec.rb b/spec/services/projects/update_repository_storage_service_spec.rb
index 123f604e7a4..ef8f166cc3f 100644
--- a/spec/services/projects/update_repository_storage_service_spec.rb
+++ b/spec/services/projects/update_repository_storage_service_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
context 'without wiki and design repository' do
let(:project) { create(:project, :repository, wiki_enabled: false) }
let(:destination) { 'test_second_storage' }
- let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, project: project, destination_storage_name: destination) }
+ let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, container: project, destination_storage_name: destination) }
let!(:checksum) { project.repository.checksum }
let(:project_repository_double) { double(:repository) }
let(:original_project_repository_double) { double(:repository) }
@@ -144,7 +144,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
end
context 'when the repository move is finished' do
- let(:repository_storage_move) { create(:project_repository_storage_move, :finished, project: project, destination_storage_name: destination) }
+ let(:repository_storage_move) { create(:project_repository_storage_move, :finished, container: project, destination_storage_name: destination) }
it 'is idempotent' do
expect do
@@ -156,7 +156,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
end
context 'when the repository move is failed' do
- let(:repository_storage_move) { create(:project_repository_storage_move, :failed, project: project, destination_storage_name: destination) }
+ let(:repository_storage_move) { create(:project_repository_storage_move, :failed, container: project, destination_storage_name: destination) }
it 'is idempotent' do
expect do
@@ -170,7 +170,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
context 'project with no repositories' do
let(:project) { create(:project) }
- let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, project: project, destination_storage_name: 'test_second_storage') }
+ let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, container: project, destination_storage_name: 'test_second_storage') }
it 'updates the database' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
@@ -191,7 +191,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
let(:project) { create(:project, :repository, wiki_enabled: true) }
let(:repository) { project.wiki.repository }
let(:destination) { 'test_second_storage' }
- let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, project: project, destination_storage_name: destination) }
+ let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, container: project, destination_storage_name: destination) }
before do
project.create_wiki
@@ -204,7 +204,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
let(:project) { create(:project, :repository) }
let(:repository) { project.design_repository }
let(:destination) { 'test_second_storage' }
- let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, project: project, destination_storage_name: destination) }
+ let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, container: project, destination_storage_name: destination) }
before do
project.design_repository.create_if_not_exists
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 1f521ed4a93..e6d1d0e90a7 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -1584,7 +1584,7 @@ RSpec.describe QuickActions::InterpretService do
end
end
- it 'limits to commands passed ' do
+ it 'limits to commands passed' do
content = "/shrug test\n/close"
text, commands = service.execute(content, issue, only: [:shrug])
@@ -1593,7 +1593,7 @@ RSpec.describe QuickActions::InterpretService do
expect(text).to eq("test #{described_class::SHRUG}\n/close")
end
- it 'preserves leading whitespace ' do
+ it 'preserves leading whitespace' do
content = " - list\n\n/close\n\ntest\n\n"
text, _ = service.execute(content, issue)
diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb
index b9294182883..7287825a0be 100644
--- a/spec/services/releases/create_service_spec.rb
+++ b/spec/services/releases/create_service_spec.rb
@@ -167,28 +167,47 @@ RSpec.describe Releases::CreateService do
end
end
- context 'when no milestone is passed in' do
- it 'creates a release without a milestone tied to it' do
- expect(params.key?(:milestones)).to be_falsey
+ context 'no milestone association behavior' do
+ let(:title_1) { 'v1.0' }
+ let(:title_2) { 'v1.0-rc' }
+ let!(:milestone_1) { create(:milestone, :active, project: project, title: title_1) }
+ let!(:milestone_2) { create(:milestone, :active, project: project, title: title_2) }
- service.execute
- release = project.releases.last
+ context 'when no milestones parameter is passed' do
+ it 'creates a release without a milestone tied to it' do
+ expect(service.param_for_milestone_titles_provided?).to be_falsey
- expect(release.milestones).to be_empty
+ service.execute
+ release = project.releases.last
+
+ expect(release.milestones).to be_empty
+ end
+
+ it 'does not create any new MilestoneRelease object' do
+ expect { service.execute }.not_to change { MilestoneRelease.count }
+ end
end
- it 'does not create any new MilestoneRelease object' do
- expect { service.execute }.not_to change { MilestoneRelease.count }
+ context 'when an empty array is passed as the milestones parameter' do
+ it 'creates a release without a milestone tied to it' do
+ service = described_class.new(project, user, params.merge!({ milestones: [] }))
+ service.execute
+ release = project.releases.last
+
+ expect(release.milestones).to be_empty
+ end
end
- end
- context 'when an empty value is passed as a milestone' do
- it 'creates a release without a milestone tied to it' do
- service = described_class.new(project, user, params.merge!({ milestones: [] }))
- service.execute
- release = project.releases.last
+ context 'when nil is passed as the milestones parameter' do
+ it 'creates a release without a milestone tied to it' do
+ expect(service.param_for_milestone_titles_provided?).to be_falsey
- expect(release.milestones).to be_empty
+ service = described_class.new(project, user, params.merge!({ milestones: nil }))
+ service.execute
+ release = project.releases.last
+
+ expect(release.milestones).to be_empty
+ end
end
end
end
@@ -217,7 +236,7 @@ RSpec.describe Releases::CreateService do
let(:released_at) { 3.weeks.ago }
it 'does not execute CreateEvidenceWorker' do
- expect { subject }.not_to change(CreateEvidenceWorker.jobs, :size)
+ expect { subject }.not_to change(Releases::CreateEvidenceWorker.jobs, :size)
end
it 'does not create an Evidence object', :sidekiq_inline do
@@ -316,7 +335,7 @@ RSpec.describe Releases::CreateService do
end
it 'queues CreateEvidenceWorker' do
- expect { subject }.to change(CreateEvidenceWorker.jobs, :size).by(1)
+ expect { subject }.to change(Releases::CreateEvidenceWorker.jobs, :size).by(1)
end
it 'creates Evidence', :sidekiq_inline do
@@ -341,18 +360,12 @@ RSpec.describe Releases::CreateService do
context 'upcoming release' do
let(:released_at) { 1.day.from_now }
- it 'queues CreateEvidenceWorker' do
- expect { subject }.to change(CreateEvidenceWorker.jobs, :size).by(1)
- end
-
- it 'queues CreateEvidenceWorker at the released_at timestamp' do
- subject
-
- expect(CreateEvidenceWorker.jobs.last['at'].to_i).to eq(released_at.to_i)
+ it 'does not execute CreateEvidenceWorker' do
+ expect { subject }.not_to change(Releases::CreateEvidenceWorker.jobs, :size)
end
- it 'creates Evidence', :sidekiq_inline do
- expect { subject }.to change(Releases::Evidence, :count).by(1)
+ it 'does not create an Evidence object', :sidekiq_inline do
+ expect { subject }.not_to change(Releases::Evidence, :count)
end
it 'is not a historical release' do
@@ -366,8 +379,6 @@ RSpec.describe Releases::CreateService do
expect(last_release.upcoming_release?).to be_truthy
end
-
- include_examples 'uses the right pipeline for evidence'
end
end
end
diff --git a/spec/services/resource_events/change_labels_service_spec.rb b/spec/services/resource_events/change_labels_service_spec.rb
index efee185669e..8eac6ae0b49 100644
--- a/spec/services/resource_events/change_labels_service_spec.rb
+++ b/spec/services/resource_events/change_labels_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe ResourceEvents::ChangeLabelsService do
describe '.change_labels' do
subject { described_class.new(resource, author).execute(added_labels: added, removed_labels: removed) }
- let(:labels) { create_list(:label, 2, project: project) }
+ let_it_be(:labels) { create_list(:label, 2, project: project) }
def expect_label_event(event, label, action)
expect(event.user).to eq(author)
@@ -57,5 +57,28 @@ RSpec.describe ResourceEvents::ChangeLabelsService do
expect { subject }.to change { resource.resource_label_events.count }.from(0).to(2)
end
end
+
+ describe 'usage data' do
+ let(:added) { [labels[0]] }
+ let(:removed) { [labels[1]] }
+
+ context 'when resource is an issue' do
+ it 'tracks changed labels' do
+ expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_label_changed_action)
+
+ subject
+ end
+ end
+
+ context 'when resource is a merge request' do
+ let(:resource) { create(:merge_request, source_project: project) }
+
+ it 'does not track changed labels' do
+ expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:track_issue_label_changed_action)
+
+ subject
+ end
+ end
+ end
end
end
diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb
index 2082a163b29..24afa83ef2c 100644
--- a/spec/services/submit_usage_ping_service_spec.rb
+++ b/spec/services/submit_usage_ping_service_spec.rb
@@ -134,10 +134,9 @@ RSpec.describe SubmitUsagePingService do
it_behaves_like 'saves DevOps report data from the response'
end
- context 'with save_raw_usage_data feature enabled' do
+ context 'with saving raw_usage_data' do
before do
stub_response(body: with_dev_ops_score_params)
- stub_feature_flags(save_raw_usage_data: true)
end
it 'creates a raw_usage_data record' do
@@ -159,18 +158,6 @@ RSpec.describe SubmitUsagePingService do
end
end
- context 'with save_raw_usage_data feature disabled' do
- before do
- stub_response(body: with_dev_ops_score_params)
- end
-
- it 'does not create a raw_usage_data record' do
- stub_feature_flags(save_raw_usage_data: false)
-
- expect { subject.execute }.to change(RawUsageData, :count).by(0)
- end
- end
-
context 'and usage ping response has unsuccessful status' do
before do
stub_response(body: nil, status: 504)
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index d8ade0fbbda..3e7594bd30f 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Suggestions::ApplyService do
position_args = args.slice(:old_path, :new_path, :old_line, :new_line)
content_args = args.slice(:from_content, :to_content)
- position = build_position(position_args)
+ position = build_position(**position_args)
diff_note = create(:diff_note_on_merge_request,
noteable: merge_request,
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index b25837ca260..2020c67f465 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -159,9 +159,6 @@ RSpec.describe SystemHooksService do
it { expect(event_name(group, :create)).to eq 'group_create' }
it { expect(event_name(group, :destroy)).to eq 'group_destroy' }
it { expect(event_name(group, :rename)).to eq 'group_rename' }
- it { expect(event_name(group_member, :create)).to eq 'user_add_to_group' }
- it { expect(event_name(group_member, :destroy)).to eq 'user_remove_from_group' }
- it { expect(event_name(group_member, :update)).to eq 'user_update_for_group' }
end
def event_data(*args)
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index a4ae7e42958..9c35f9e3817 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -333,6 +333,19 @@ RSpec.describe SystemNoteService do
end
end
+ describe '.noteable_cloned' do
+ let(:noteable_ref) { double }
+ let(:direction) { double }
+
+ it 'calls IssuableService' do
+ expect_next_instance_of(::SystemNotes::IssuablesService) do |service|
+ expect(service).to receive(:noteable_cloned).with(noteable_ref, direction)
+ end
+
+ described_class.noteable_cloned(double, double, noteable_ref, double, direction: direction)
+ end
+ end
+
describe 'Jira integration' do
include JiraServiceHelper
diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb
index b70c5e899fc..96afca2f2cb 100644
--- a/spec/services/system_notes/issuables_service_spec.rb
+++ b/spec/services/system_notes/issuables_service_spec.rb
@@ -522,6 +522,91 @@ RSpec.describe ::SystemNotes::IssuablesService do
end
end
+ describe '#noteable_cloned' do
+ let(:new_project) { create(:project) }
+ let(:new_noteable) { create(:issue, project: new_project) }
+
+ subject do
+ service.noteable_cloned(new_noteable, direction)
+ end
+
+ shared_examples 'cross project mentionable' do
+ include MarkupHelper
+
+ it 'contains cross reference to new noteable' do
+ expect(subject.note).to include cross_project_reference(new_project, new_noteable)
+ end
+
+ it 'mentions referenced noteable' do
+ expect(subject.note).to include new_noteable.to_reference
+ end
+
+ it 'mentions referenced project' do
+ expect(subject.note).to include new_project.full_path
+ end
+ end
+
+ context 'cloned to' do
+ let(:direction) { :to }
+
+ it_behaves_like 'cross project mentionable'
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'cloned' }
+ end
+
+ it 'notifies about noteable being cloned to' do
+ expect(subject.note).to match('cloned to')
+ end
+ end
+
+ context 'cloned from' do
+ let(:direction) { :from }
+
+ it_behaves_like 'cross project mentionable'
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'cloned' }
+ end
+
+ it 'notifies about noteable being cloned from' do
+ expect(subject.note).to match('cloned from')
+ end
+ end
+
+ context 'invalid direction' do
+ let(:direction) { :invalid }
+
+ it 'raises error' do
+ expect { subject }.to raise_error StandardError, /Invalid direction/
+ end
+ end
+
+ context 'metrics' do
+ context 'cloned from' do
+ let(:direction) { :from }
+
+ it 'does not tracks usage' do
+ expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter)
+ .not_to receive(:track_issue_cloned_action).with(author: author)
+
+ subject
+ end
+ end
+
+ context 'cloned to' do
+ let(:direction) { :to }
+
+ it 'tracks usage' do
+ expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter)
+ .to receive(:track_issue_cloned_action).with(author: author)
+
+ subject
+ end
+ end
+ end
+ end
+
describe '#mark_duplicate_issue' do
subject { service.mark_duplicate_issue(canonical_issue) }
diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb
index 9040966215c..74340bac055 100644
--- a/spec/services/users/create_service_spec.rb
+++ b/spec/services/users/create_service_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe Users::CreateService do
service.execute
end
- it 'executes system hooks ' do
+ it 'executes system hooks' do
system_hook_service = spy(:system_hook_service)
expect(service).to receive(:system_hook_service).and_return(system_hook_service)
diff --git a/spec/services/users/reject_service_spec.rb b/spec/services/users/reject_service_spec.rb
new file mode 100644
index 00000000000..07863d1a290
--- /dev/null
+++ b/spec/services/users/reject_service_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::RejectService do
+ let_it_be(:current_user) { create(:admin) }
+ let(:user) { create(:user, :blocked_pending_approval) }
+
+ subject(:execute) { described_class.new(current_user).execute(user) }
+
+ describe '#execute' do
+ context 'failures' do
+ context 'when the executor user is not allowed to reject users' do
+ let(:current_user) { create(:user) }
+
+ it 'returns error result' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to match(/You are not allowed to reject a user/)
+ end
+ end
+
+ context 'when the executor user is an admin in admin mode', :enable_admin_mode do
+ context 'when user is not in pending approval state' do
+ let(:user) { create(:user, state: 'active') }
+
+ it 'returns error result' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message])
+ .to match(/This user does not have a pending request/)
+ end
+ end
+ end
+ end
+
+ context 'success' do
+ context 'when the executor user is an admin in admin mode', :enable_admin_mode do
+ it 'deletes the user', :sidekiq_inline do
+ subject
+
+ expect(subject[:status]).to eq(:success)
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'emails the user on rejection' do
+ expect_next_instance_of(NotificationService) do |notification|
+ allow(notification).to receive(:user_admin_rejection).with(user.name, user.notification_email)
+ end
+
+ subject
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/users/set_status_service_spec.rb b/spec/services/users/set_status_service_spec.rb
index 69cd647eaeb..2c776a0eeb4 100644
--- a/spec/services/users/set_status_service_spec.rb
+++ b/spec/services/users/set_status_service_spec.rb
@@ -31,19 +31,28 @@ RSpec.describe Users::SetStatusService do
expect(service.execute).to be(true)
end
+ context 'when setting availability to not_set' do
+ before do
+ params[:availability] = 'not_set'
+
+ create(:user_status, user: current_user, availability: 'busy')
+ end
+
+ it 'updates the availability' do
+ expect { service.execute }.to change { current_user.status.availability }.from('busy').to('not_set')
+ end
+ end
+
context 'when the given availability value is not valid' do
- let(:params) { { availability: 'not a valid value' } }
+ before do
+ params[:availability] = 'not a valid value'
+ end
it 'does not update the status' do
user_status = create(:user_status, user: current_user)
expect { service.execute }.not_to change { user_status.reload }
end
-
- it 'returns false' do
- create(:user_status, user: current_user)
- expect(service.execute).to be(false)
- end
end
context 'for another user' do
@@ -69,11 +78,21 @@ RSpec.describe Users::SetStatusService do
context 'without params' do
let(:params) { {} }
- it 'deletes the status' do
- status = create(:user_status, user: current_user)
+ shared_examples 'removes user status record' do
+ it 'deletes the status' do
+ status = create(:user_status, user: current_user)
+
+ expect { service.execute }
+ .to change { current_user.reload.status }.from(status).to(nil)
+ end
+ end
+
+ it_behaves_like 'removes user status record'
+
+ context 'when not_set is given for availability' do
+ let(:params) { { availability: 'not_set' } }
- expect { service.execute }
- .to change { current_user.reload.status }.from(status).to(nil)
+ it_behaves_like 'removes user status record'
end
end
end
diff --git a/spec/services/users/validate_otp_service_spec.rb b/spec/services/users/validate_otp_service_spec.rb
index 826755d6145..42f0c10488c 100644
--- a/spec/services/users/validate_otp_service_spec.rb
+++ b/spec/services/users/validate_otp_service_spec.rb
@@ -20,7 +20,8 @@ RSpec.describe Users::ValidateOtpService do
context 'FortiAuthenticator' do
before do
- stub_feature_flags(forti_authenticator: true)
+ stub_feature_flags(forti_authenticator: user)
+ allow(::Gitlab.config.forti_authenticator).to receive(:enabled).and_return(true)
end
it 'calls FortiAuthenticator strategy' do
@@ -31,4 +32,19 @@ RSpec.describe Users::ValidateOtpService do
validate
end
end
+
+ context 'FortiTokenCloud' do
+ before do
+ stub_feature_flags(forti_token_cloud: user)
+ allow(::Gitlab.config.forti_token_cloud).to receive(:enabled).and_return(true)
+ end
+
+ it 'calls FortiTokenCloud strategy' do
+ expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::FortiTokenCloud) do |strategy|
+ expect(strategy).to receive(:validate).with(otp_code).once
+ end
+
+ validate
+ end
+ end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 38e3f851116..c19c26f9a0b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -8,6 +8,12 @@ if $".include?(File.expand_path('fast_spec_helper.rb', __dir__))
abort 'Aborting...'
end
+# Enable deprecation warnings by default and make them more visible
+# to developers to ease upgrading to newer Ruby versions.
+Warning[:deprecated] = true unless ENV.key?('SILENCE_DEPRECATIONS')
+
+require './spec/deprecation_toolkit_env'
+
require './spec/simplecov_env'
SimpleCovEnv.start!
@@ -134,8 +140,11 @@ RSpec.configure do |config|
config.include NextFoundInstanceOf
config.include NextInstanceOf
config.include TestEnv
+ config.include FileReadHelpers
config.include Devise::Test::ControllerHelpers, type: :controller
+ config.include Devise::Test::ControllerHelpers, type: :view
config.include Devise::Test::IntegrationHelpers, type: :feature
+ config.include Devise::Test::IntegrationHelpers, type: :request
config.include LoginHelpers, type: :feature
config.include SearchHelpers, type: :feature
config.include WaitHelpers, type: :feature
@@ -143,7 +152,6 @@ RSpec.configure do |config|
config.include EmailHelpers, :mailer, type: :mailer
config.include Warden::Test::Helpers, type: :request
config.include Gitlab::Routing, type: :routing
- config.include Devise::Test::ControllerHelpers, type: :view
config.include ApiHelpers, :api
config.include CookieHelper, :js
config.include InputHelper, :js
@@ -215,10 +223,6 @@ RSpec.configure do |config|
stub_feature_flags(vue_issuable_sidebar: false)
stub_feature_flags(vue_issuable_epic_sidebar: false)
- # The following can be removed once we are confident the
- # unified diff lines works as expected
- stub_feature_flags(unified_diff_lines: false)
-
# Merge request widget GraphQL requests are disabled in the tests
# for now whilst we migrate as much as we can over the GraphQL
stub_feature_flags(merge_request_widget_graphql: false)
@@ -227,6 +231,10 @@ RSpec.configure do |config|
# tests, until we introduce it in user settings
stub_feature_flags(forti_authenticator: false)
+ # Using FortiToken Cloud as OTP provider is disabled by default in
+ # tests, until we introduce it in user settings
+ stub_feature_flags(forti_token_cloud: false)
+
enable_rugged = example.metadata[:enable_rugged].present?
# Disable Rugged features by default
@@ -238,6 +246,8 @@ RSpec.configure do |config|
# See https://gitlab.com/gitlab-org/gitlab/-/issues/33867
stub_feature_flags(file_identifier_hash: false)
+ stub_feature_flags(unified_diff_components: false)
+
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged)
else
unstub_all_feature_flags
@@ -279,27 +289,15 @@ RSpec.configure do |config|
# context 'some test in mocked dir', :do_not_mock_admin_mode do ... end
admin_mode_mock_dirs = %w(
./ee/spec/elastic_integration
- ./ee/spec/features
./ee/spec/finders
./ee/spec/lib
- ./ee/spec/requests/admin
./ee/spec/serializers
- ./ee/spec/support/protected_tags
- ./ee/spec/support/shared_examples/features
./ee/spec/support/shared_examples/finders/geo
./ee/spec/support/shared_examples/graphql/geo
- ./spec/features
./spec/finders
- ./spec/frontend
- ./spec/helpers
./spec/lib
- ./spec/requests
./spec/serializers
- ./spec/support/protected_tags
- ./spec/support/shared_examples/features
- ./spec/support/shared_examples/requests
./spec/support/shared_examples/lib/gitlab
- ./spec/views
./spec/workers
)
@@ -333,6 +331,13 @@ RSpec.configure do |config|
Gitlab::WithRequestStore.with_request_store { example.run }
end
+ config.before(:example, :request_store) do
+ # Clear request store before actually starting the spec (the
+ # `around` above will have the request store enabled for all
+ # `before` blocks)
+ RequestStore.clear!
+ end
+
config.around do |example|
# Wrap each example in it's own context to make sure the contexts don't
# leak
diff --git a/spec/support/atlassian/jira_connect/schemata.rb b/spec/support/atlassian/jira_connect/schemata.rb
new file mode 100644
index 00000000000..91f8fe0bb41
--- /dev/null
+++ b/spec/support/atlassian/jira_connect/schemata.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+module Atlassian
+ module Schemata
+ def self.build_info
+ {
+ 'type' => 'object',
+ 'required' => %w(schemaVersion pipelineId buildNumber updateSequenceNumber displayName url state issueKeys testInfo references),
+ 'properties' => {
+ 'schemaVersion' => { 'type' => 'string', 'pattern' => '1.0' },
+ 'pipelineId' => { 'type' => 'string' },
+ 'buildNumber' => { 'type' => 'integer' },
+ 'updateSequenceNumber' => { 'type' => 'integer' },
+ 'displayName' => { 'type' => 'string' },
+ 'url' => { 'type' => 'string' },
+ 'state' => {
+ 'type' => 'string',
+ 'pattern' => '(pending|in_progress|successful|failed|cancelled)'
+ },
+ 'issueKeys' => {
+ 'type' => 'array',
+ 'items' => { 'type' => 'string' },
+ 'minItems' => 1
+ },
+ 'testInfo' => {
+ 'type' => 'object',
+ 'required' => %w(totalNumber numberPassed numberFailed numberSkipped),
+ 'properties' => {
+ 'totalNumber' => { 'type' => 'integer' },
+ 'numberFailed' => { 'type' => 'integer' },
+ 'numberPassed' => { 'type' => 'integer' },
+ 'numberSkipped' => { 'type' => 'integer' }
+ }
+ },
+ 'references' => {
+ 'type' => 'array',
+ 'items' => {
+ 'type' => 'object',
+ 'required' => %w(commit ref),
+ 'properties' => {
+ 'commit' => {
+ 'type' => 'object',
+ 'required' => %w(id repositoryUri),
+ 'properties' => {
+ 'id' => { 'type' => 'string' },
+ 'repositoryUri' => { 'type' => 'string' }
+ }
+ },
+ 'ref' => {
+ 'type' => 'object',
+ 'required' => %w(name uri),
+ 'properties' => {
+ 'name' => { 'type' => 'string' },
+ 'uri' => { 'type' => 'string' }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ end
+
+ def self.build_info_payload
+ {
+ 'type' => 'object',
+ 'required' => %w(providerMetadata builds),
+ 'properties' => {
+ 'providerMetadata' => provider_metadata,
+ 'builds' => { 'type' => 'array', 'items' => build_info }
+ }
+ }
+ end
+
+ def self.provider_metadata
+ {
+ 'type' => 'object',
+ 'required' => %w(product),
+ 'properties' => { 'product' => { 'type' => 'string' } }
+ }
+ end
+ end
+end
diff --git a/spec/support/caching.rb b/spec/support/caching.rb
index 883d531550a..11e4f534971 100644
--- a/spec/support/caching.rb
+++ b/spec/support/caching.rb
@@ -25,7 +25,7 @@ RSpec.configure do |config|
original_null_store = Rails.cache
caching_config_hash = Gitlab::Redis::Cache.params
caching_config_hash[:namespace] = Gitlab::Redis::Cache::CACHE_NAMESPACE
- Rails.cache = ActiveSupport::Cache::RedisCacheStore.new(caching_config_hash)
+ Rails.cache = ActiveSupport::Cache::RedisCacheStore.new(**caching_config_hash)
redis_cache_cleanup!
diff --git a/spec/support/gitlab_experiment.rb b/spec/support/gitlab_experiment.rb
new file mode 100644
index 00000000000..1f283e4f06c
--- /dev/null
+++ b/spec/support/gitlab_experiment.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+# Disable all caching for experiments in tests.
+Gitlab::Experiment::Configuration.cache = nil
diff --git a/spec/support/gitlab_stubs/gitlab_ci_includes.yml b/spec/support/gitlab_stubs/gitlab_ci_includes.yml
new file mode 100644
index 00000000000..e74773ce23e
--- /dev/null
+++ b/spec/support/gitlab_stubs/gitlab_ci_includes.yml
@@ -0,0 +1,19 @@
+rspec 0 1:
+ stage: build
+ script: 'rake spec'
+ needs: []
+
+rspec 0 2:
+ stage: build
+ script: 'rake spec'
+ needs: []
+
+spinach:
+ stage: build
+ script: 'rake spinach'
+ needs: []
+
+docker:
+ stage: test
+ script: 'curl http://dockerhub/URL'
+ needs: [spinach, rspec 0 1]
diff --git a/spec/support/graphql/arguments.rb b/spec/support/graphql/arguments.rb
new file mode 100644
index 00000000000..d8c334c2ca4
--- /dev/null
+++ b/spec/support/graphql/arguments.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Graphql
+ class Arguments
+ delegate :blank?, :empty?, to: :to_h
+
+ def initialize(values)
+ @values = values.compact
+ end
+
+ def to_h
+ @values
+ end
+
+ def ==(other)
+ to_h == other&.to_h
+ end
+
+ alias_method :eql, :==
+
+ def to_s
+ return '' if empty?
+
+ @values.map do |name, value|
+ value_str = as_graphql_literal(value)
+
+ "#{GraphqlHelpers.fieldnamerize(name.to_s)}: #{value_str}"
+ end.join(", ")
+ end
+
+ def as_graphql_literal(value)
+ self.class.as_graphql_literal(value)
+ end
+
+ # Transform values to GraphQL literal arguments.
+ # Use symbol for Enum values
+ def self.as_graphql_literal(value)
+ case value
+ when ::Graphql::Arguments then "{#{value}}"
+ when Array then "[#{value.map { |v| as_graphql_literal(v) }.join(',')}]"
+ when Hash then "{#{new(value)}}"
+ when Integer, Float, Symbol then value.to_s
+ when String then "\"#{value.gsub(/"/, '\\"')}\""
+ when nil then 'null'
+ when true then 'true'
+ when false then 'false'
+ else
+ value.to_graphql_value
+ end
+ rescue NoMethodError
+ raise ArgumentError, "Cannot represent #{value} as GraphQL literal"
+ end
+
+ def merge(other)
+ self.class.new(@values.merge(other.to_h))
+ end
+
+ def +(other)
+ if blank?
+ other
+ elsif other.blank?
+ self
+ elsif other.is_a?(String)
+ [to_s, other].compact.join(', ')
+ else
+ merge(other)
+ end
+ end
+ end
+end
diff --git a/spec/support/graphql/field_inspection.rb b/spec/support/graphql/field_inspection.rb
new file mode 100644
index 00000000000..f39ba751141
--- /dev/null
+++ b/spec/support/graphql/field_inspection.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Graphql
+ class FieldInspection
+ def initialize(field)
+ @field = field
+ end
+
+ def nested_fields?
+ !scalar? && !enum?
+ end
+
+ def scalar?
+ type.kind.scalar?
+ end
+
+ def enum?
+ type.kind.enum?
+ end
+
+ def type
+ @type ||= begin
+ field_type = @field.type.respond_to?(:to_graphql) ? @field.type.to_graphql : @field.type
+
+ # The type could be nested. For example `[GraphQL::STRING_TYPE]`:
+ # - List
+ # - String!
+ # - String
+ field_type = field_type.of_type while field_type.respond_to?(:of_type)
+
+ field_type
+ end
+ end
+ end
+end
diff --git a/spec/support/graphql/field_selection.rb b/spec/support/graphql/field_selection.rb
new file mode 100644
index 00000000000..e2a3334acac
--- /dev/null
+++ b/spec/support/graphql/field_selection.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+module Graphql
+ class FieldSelection
+ delegate :empty?, :blank?, :to_h, to: :selection
+ delegate :size, to: :paths
+
+ attr_reader :selection
+
+ def initialize(selection = {})
+ @selection = selection.to_h
+ end
+
+ def to_s
+ serialize_field_selection(selection)
+ end
+
+ def paths
+ selection.flat_map do |field, subselection|
+ paths_in([field], subselection)
+ end
+ end
+
+ private
+
+ def paths_in(path, leaves)
+ return [path] if leaves.nil?
+
+ leaves.to_a.flat_map do |k, v|
+ paths_in([k], v).map { |tail| path + tail }
+ end
+ end
+
+ def serialize_field_selection(hash, level = 0)
+ indent = ' ' * level
+
+ hash.map do |field, subselection|
+ if subselection.nil?
+ "#{indent}#{field}"
+ else
+ subfields = serialize_field_selection(subselection, level + 1)
+ "#{indent}#{field} {\n#{subfields}\n#{indent}}"
+ end
+ end.join("\n")
+ end
+
+ NO_SKIP = ->(_name, _field) { false }
+
+ def self.select_fields(type, skip = NO_SKIP, parent_types = Set.new, max_depth = 3)
+ return new if max_depth <= 0
+
+ new(type.fields.flat_map do |name, field|
+ next [] if skip[name, field]
+
+ inspected = ::Graphql::FieldInspection.new(field)
+ singular_field_type = inspected.type
+
+ # If field type is the same as parent type, then we're hitting into
+ # mutual dependency. Break it from infinite recursion
+ next [] if parent_types.include?(singular_field_type)
+
+ if inspected.nested_fields?
+ subselection = select_fields(singular_field_type, skip, parent_types | [type], max_depth - 1)
+ next [] if subselection.empty?
+
+ [[name, subselection.to_h]]
+ else
+ [[name, nil]]
+ end
+ end)
+ end
+ end
+end
diff --git a/spec/support/graphql/var.rb b/spec/support/graphql/var.rb
new file mode 100644
index 00000000000..4f2c774e898
--- /dev/null
+++ b/spec/support/graphql/var.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Graphql
+ # Helper to pass variables around generated queries.
+ #
+ # e.g.:
+ # first = var('Int')
+ # after = var('String')
+ #
+ # query = with_signature(
+ # [first, after],
+ # query_graphql_path([
+ # [:project, { full_path: project.full_path }],
+ # [:issues, { after: after, first: first }]
+ # :nodes
+ # ], all_graphql_fields_for('Issue'))
+ # )
+ #
+ # post_graphql(query, variables: [first.with(2), after.with(some_cursor)])
+ #
+ class Var
+ attr_reader :name, :type
+ attr_accessor :value
+
+ def initialize(name, type)
+ @name = name
+ @type = type
+ end
+
+ def sig
+ "#{to_graphql_value}: #{type}"
+ end
+
+ def to_graphql_value
+ "$#{name}"
+ end
+
+ # We return a new object so that running the same query twice with
+ # different values does not risk re-using the value
+ #
+ # e.g.
+ #
+ # x = var('Int')
+ # expect { post_graphql(query, variables: x) }
+ # .to issue_same_number_of_queries_as { post_graphql(query, variables: x.with(1)) }
+ #
+ # Here we post the `x` variable once with the value set to 1, and once with
+ # the value set to `nil`.
+ def with(value)
+ copy = Var.new(name, type)
+ copy.value = value
+ copy
+ end
+
+ def to_h
+ { name => value }
+ end
+ end
+end
diff --git a/spec/support/helpers/after_next_helpers.rb b/spec/support/helpers/after_next_helpers.rb
new file mode 100644
index 00000000000..0a7844fdd8f
--- /dev/null
+++ b/spec/support/helpers/after_next_helpers.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require_relative './next_instance_of'
+
+module AfterNextHelpers
+ class DeferredExpectation
+ include ::NextInstanceOf
+ include ::RSpec::Matchers
+ include ::RSpec::Mocks::ExampleMethods
+
+ def initialize(klass, args, level:)
+ @klass = klass
+ @args = args
+ @level = level.to_sym
+ end
+
+ def to(condition)
+ run_condition(condition, asserted: true)
+ end
+
+ def not_to(condition)
+ run_condition(condition, asserted: false)
+ end
+
+ private
+
+ attr_reader :klass, :args, :level
+
+ def run_condition(condition, asserted:)
+ msg = asserted ? :to : :not_to
+ case level
+ when :expect
+ if asserted
+ expect_next_instance_of(klass, *args) { |instance| expect(instance).send(msg, condition) }
+ else
+ allow_next_instance_of(klass, *args) { |instance| expect(instance).send(msg, condition) }
+ end
+ when :allow
+ allow_next_instance_of(klass, *args) { |instance| allow(instance).send(msg, condition) }
+ else
+ raise "Unknown level: #{level}"
+ end
+ end
+ end
+
+ def allow_next(klass, *args)
+ DeferredExpectation.new(klass, args, level: :allow)
+ end
+
+ def expect_next(klass, *args)
+ DeferredExpectation.new(klass, args, level: :expect)
+ end
+end
diff --git a/spec/support/helpers/database_helpers.rb b/spec/support/helpers/database_helpers.rb
new file mode 100644
index 00000000000..e9f0a74a8d1
--- /dev/null
+++ b/spec/support/helpers/database_helpers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module DatabaseHelpers
+ # In order to directly work with views using factories,
+ # we can swapout the view for a table of identical structure.
+ def swapout_view_for_table(view)
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ CREATE TABLE #{view}_copy (LIKE #{view});
+ DROP VIEW #{view};
+ ALTER TABLE #{view}_copy RENAME TO #{view};
+ SQL
+ end
+end
diff --git a/spec/support/helpers/dependency_proxy_helpers.rb b/spec/support/helpers/dependency_proxy_helpers.rb
index 545b9d1f4d0..ebb849628bf 100644
--- a/spec/support/helpers/dependency_proxy_helpers.rb
+++ b/spec/support/helpers/dependency_proxy_helpers.rb
@@ -11,11 +11,18 @@ module DependencyProxyHelpers
.to_return(status: status, body: body || auth_body)
end
- def stub_manifest_download(image, tag, status = 200, body = nil)
+ def stub_manifest_download(image, tag, status: 200, body: nil, headers: {})
manifest_url = registry.manifest_url(image, tag)
stub_full_request(manifest_url)
- .to_return(status: status, body: body || manifest)
+ .to_return(status: status, body: body || manifest, headers: headers)
+ end
+
+ def stub_manifest_head(image, tag, status: 200, body: nil, digest: '123456')
+ manifest_url = registry.manifest_url(image, tag)
+
+ stub_full_request(manifest_url, method: :head)
+ .to_return(status: status, body: body, headers: { 'docker-content-digest' => digest } )
end
def stub_blob_download(image, blob_sha, status = 200, body = '123456')
@@ -25,6 +32,13 @@ module DependencyProxyHelpers
.to_return(status: status, body: body)
end
+ def build_jwt(user = nil, expire_time: nil)
+ JSONWebToken::HMACToken.new(::Auth::DependencyProxyAuthenticationService.secret).tap do |jwt|
+ jwt['user_id'] = user.id if user
+ jwt.expire_time = expire_time || jwt.issued_at + 1.minute
+ end
+ end
+
private
def registry
diff --git a/spec/support/helpers/features/merge_request_helpers.rb b/spec/support/helpers/features/merge_request_helpers.rb
new file mode 100644
index 00000000000..53896e1fe12
--- /dev/null
+++ b/spec/support/helpers/features/merge_request_helpers.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Spec
+ module Support
+ module Helpers
+ module Features
+ module MergeRequestHelpers
+ def preload_view_requirements(merge_request, note)
+ # This will load the status fields of the author of the note and merge request
+ # to avoid queries when rendering the view being tested.
+ #
+ merge_request.author.status
+ note.author.status
+ end
+
+ def serialize_issuable_sidebar(user, project, merge_request)
+ MergeRequestSerializer
+ .new(current_user: user, project: project)
+ .represent(merge_request, serializer: 'sidebar')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/features/web_ide_spec_helpers.rb b/spec/support/helpers/features/web_ide_spec_helpers.rb
index 12d3cecd052..358bfacce05 100644
--- a/spec/support/helpers/features/web_ide_spec_helpers.rb
+++ b/spec/support/helpers/features/web_ide_spec_helpers.rb
@@ -22,8 +22,6 @@ module WebIdeSpecHelpers
click_link('Web IDE')
wait_for_requests
-
- save_monaco_editor_reference
end
def ide_tree_body
@@ -65,17 +63,6 @@ module WebIdeSpecHelpers
ide_set_editor_value(content)
end
- def ide_rename_file(path, new_path)
- container = ide_traverse_to_file(path)
-
- click_file_action(container, 'Rename/Move')
-
- within '#ide-new-entry' do
- find('input').fill_in(with: new_path)
- click_button('Rename file')
- end
- end
-
# Deletes a file by traversing to `path`
# then clicking the 'Delete' action.
#
@@ -103,20 +90,6 @@ module WebIdeSpecHelpers
container
end
- def ide_close_file(name)
- within page.find('.multi-file-tabs') do
- click_button("Close #{name}")
- end
- end
-
- def ide_open_file(path)
- row = ide_traverse_to_file(path)
-
- ide_open_file_row(row)
-
- wait_for_requests
- end
-
def ide_open_file_row(row)
return if ide_folder_row_open?(row)
@@ -130,10 +103,6 @@ module WebIdeSpecHelpers
execute_script("monaco.editor.getModel('#{uri}').setValue('#{escape_javascript(value)}')")
end
- def ide_set_editor_position(line, col)
- execute_script("TEST_EDITOR.setPosition(#{{ lineNumber: line, column: col }.to_json})")
- end
-
def ide_editor_value
editor = find('.monaco-editor')
uri = editor['data-uri']
@@ -180,8 +149,4 @@ module WebIdeSpecHelpers
wait_for_requests
end
end
-
- def save_monaco_editor_reference
- evaluate_script("monaco.editor.onDidCreateEditor(editor => { window.TEST_EDITOR = editor; })")
- end
end
diff --git a/spec/support/helpers/file_read_helpers.rb b/spec/support/helpers/file_read_helpers.rb
new file mode 100644
index 00000000000..c30a9e6466f
--- /dev/null
+++ b/spec/support/helpers/file_read_helpers.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module FileReadHelpers
+ def stub_file_read(file, content: nil, error: nil)
+ allow_original_file_read
+
+ expectation = allow(File).to receive(:read).with(file)
+
+ if error
+ expectation.and_raise(error)
+ elsif content
+ expectation.and_return(content)
+ else
+ expectation
+ end
+ end
+
+ def expect_file_read(file, content: nil, error: nil)
+ allow_original_file_read
+
+ expectation = expect(File).to receive(:read).with(file)
+
+ if error
+ expectation.and_raise(error)
+ elsif content
+ expectation.and_return(content)
+ else
+ expectation
+ end
+ end
+
+ def expect_file_not_to_read(file)
+ allow_original_file_read
+
+ expect(File).not_to receive(:read).with(file)
+ end
+
+ private
+
+ def allow_original_file_read
+ # Don't set this mock twice, otherwise subsequent calls will clobber
+ # previous mocks
+ return if @allow_original_file_read
+
+ @allow_original_file_read = true
+ allow(File).to receive(:read).and_call_original
+ end
+end
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index d203ff60cc9..10068b9c508 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -113,6 +113,10 @@ module FilteredSearchHelpers
create_token('Assignee', assignee_name)
end
+ def reviewer_token(reviewer_name = nil)
+ create_token('Reviewer', reviewer_name)
+ end
+
def milestone_token(milestone_name = nil, has_symbol = true, operator = '=')
symbol = has_symbol ? '%' : nil
create_token('Milestone', milestone_name, symbol, operator)
diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb
index c9c1c4dcfc9..c9b4e06f067 100644
--- a/spec/support/helpers/git_http_helpers.rb
+++ b/spec/support/helpers/git_http_helpers.rb
@@ -5,45 +5,45 @@ require_relative 'workhorse_helpers'
module GitHttpHelpers
include WorkhorseHelpers
- def clone_get(project, **options)
- get "/#{project}/info/refs", params: { service: 'git-upload-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ def clone_get(repository_path, **options)
+ get "/#{repository_path}/info/refs", params: { service: 'git-upload-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
- def clone_post(project, **options)
- post "/#{project}/git-upload-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ def clone_post(repository_path, **options)
+ post "/#{repository_path}/git-upload-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
- def push_get(project, **options)
- get "/#{project}/info/refs", params: { service: 'git-receive-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ def push_get(repository_path, **options)
+ get "/#{repository_path}/info/refs", params: { service: 'git-receive-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
- def push_post(project, **options)
- post "/#{project}/git-receive-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ def push_post(repository_path, **options)
+ post "/#{repository_path}/git-receive-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
- def download(project, user: nil, password: nil, spnego_request_token: nil)
+ def download(repository_path, user: nil, password: nil, spnego_request_token: nil)
args = { user: user, password: password, spnego_request_token: spnego_request_token }
- clone_get(project, **args)
+ clone_get(repository_path, **args)
yield response
- clone_post(project, **args)
+ clone_post(repository_path, **args)
yield response
end
- def upload(project, user: nil, password: nil, spnego_request_token: nil)
+ def upload(repository_path, user: nil, password: nil, spnego_request_token: nil)
args = { user: user, password: password, spnego_request_token: spnego_request_token }
- push_get(project, **args)
+ push_get(repository_path, **args)
yield response
- push_post(project, **args)
+ push_post(repository_path, **args)
yield response
end
- def download_or_upload(project, **args, &block)
- download(project, **args, &block)
- upload(project, **args, &block)
+ def download_or_upload(repository_path, **args, &block)
+ download(repository_path, **args, &block)
+ upload(repository_path, **args, &block)
end
def auth_env(user, password, spnego_request_token)
diff --git a/spec/support/helpers/gitlab_verify_helpers.rb b/spec/support/helpers/gitlab_verify_helpers.rb
index 9901ce374ed..2a6ba8aaff4 100644
--- a/spec/support/helpers/gitlab_verify_helpers.rb
+++ b/spec/support/helpers/gitlab_verify_helpers.rb
@@ -2,7 +2,7 @@
module GitlabVerifyHelpers
def collect_ranges(args = {})
- verifier = described_class.new(args.merge(batch_size: 1))
+ verifier = described_class.new(**args.merge(batch_size: 1))
collect_results(verifier).map { |range, _| range }
end
diff --git a/spec/support/helpers/gpg_helpers.rb b/spec/support/helpers/gpg_helpers.rb
index f4df1cf601c..389e5818dbe 100644
--- a/spec/support/helpers/gpg_helpers.rb
+++ b/spec/support/helpers/gpg_helpers.rb
@@ -144,6 +144,145 @@ module GpgHelpers
'5F7EA3981A5845B141ABD522CCFBE19F00AC8B1D'
end
+ def secret_key2
+ <<~KEY.strip
+ -----BEGIN PGP PRIVATE KEY BLOCK-----
+
+ lQWGBF+7O0oBDADvRto4K9PT83Lbyp/qaMPIzBbXHB6ljdDoyb+Pn2UrHk9MhB5v
+ bTgBv+rctOabmimPPalcyaxOQ1GtrYizo1l33YQZupSvaOoStVLWqnBx8eKKcUv8
+ QucS3S2qFhj9G0tdHW7RW2BGrSwEM09d2xFsFKKAj/4RTTU5idYWrvB24DNcrBh+
+ iKsoa+rmJf1bwL6Mn9f9NwzundG16qibY/UwMlltQriWaVMn2AKVuu6HrX9pe3g5
+ Er2Szjc7DZitt6eAy3PmuWHXzDCCvsO7iPxXlywY49hLhDen3/Warwn1pSbp+im4
+ /0oJExLZBSS1xHbRSQoR6matF0+V/6TQz8Yo3g8z9HgyEtn1V7QJo3PoNrnEl73e
+ 9yslTqVtzba0Q132oRoO7eEYf82KrPOmVGj6Q9LpSXFLfsl3GlPgoBxRZXpT62CV
+ 3rGalIa2yKmcBQtyICjR1+PTIAJcVIPyr92xTo4RfLwVFW0czX7LM2H0FT2Ksj7L
+ U450ewBz8N6bFDMAEQEAAf4HAwIkqHaeA9ofAv9oQj+upbqfdEmXd0krBv5R1Q3u
+ VZwtCdnf0KGtueJ7SpPHVbNB0gCYnYdgf59MF9HHuVjHTWCOBwBJ3hmc7Yt2NcZy
+ ow15C+2xy+6/ChIYz3K7cr3jFR17M8Rz430YpCeGdYq5CfNQvNlzHDjO7PClLOek
+ jqy7V0ME0j6Q5+gHKqz6ragrUkfQBK863T4/4IUE+oCcDkuPaQUJQcYbI81R60Tl
+ 4Rasi6njwj9MZlt9k8wfXmMInWAl7aLaEzTpwVFG8xZ5IHExWGHO9mS+DNqBRVd9
+ oDQoYoLFW6w0wPIkcn1uoUJaDZoRFzy2AzFInS8oLPAYWg/Wg8TLyyTIHYq9Zn+B
+ 1mXeBHqx+TOCFq8P1wk9/A4MIl8cJmsEYrd2u0xdbVUQxCDzqrjqVmU4oamY6N6s
+ JPSp/hhBJB97CbCIoACB3aaH1CFDyXvyiqjobD5daKz8FlDzm4yze5n5b7CLwAWB
+ IA7nbNsGnLZiKQs+jmA6VcAax3nlulhG0YnzNLlwX4PgWjwjtd79rEmSdN9LsZE3
+ R26377QFE6G5NLDiKg/96NsRYA1BsDnAWKpm64ZVHHbBxz/HiAP1Zncw3Ij5p8F1
+ mtHK++qNF1P2OkAP01KaE2v6T+d3lCQzlPwnQIojW/NGvBZXarjV3916fN7rJamf
+ gs6Q72XKuXCOVJxGvknVGjXS97AIWbllLcCG5nYZx5BYaehMWOjrB9abD3h3lRXt
+ lT43gOFI53XY/vTw+jsPeT125QjjB3Kih5Ch5b6tXMj7X1Lkd9yTOIU0LVF5e9St
+ 1mvVl+pPwWafq60vlCtEnluwcEmH6XDiIABHDchgBdk+qsvc215bspyPRy4CRVAg
+ V3eaFFKgFrF/qDtzLgYVopcij1ovGmmox+m3mua4wSAs5Bm2UotEZfGscN6sCSfR
+ KAk83bV00rfjC/Zrgx3zn6PUqit5KcpLkQIo/CzUr9UCRC3tMIzFARbmjTE7f471
+ +kUuJGxMONiRQC3ejLDZ/+B7WvZm44KffyKVlOSfG0MDUZzsINNY3jUskF2pfuq2
+ acXqcVi16grRjyIsoRtZFM5/yu7ED7j4yZRRnBjD+E03uui5Rv3uiHcddE8nwwU+
+ Tctvua+0QtS5NzFL6pM8tYdgRTXYekaoZf6N8sE3kgOlanvyXwxguNA7Y5Ns1mFC
+ JqIwOVwQbi8bk9I2PY9ER/nK6HRx2LpM466wRp7Bn9WAY8k/5gjzZrqVDCZJjuTO
+ mmhvGcm9wvsXxfb1NQdhc7ZHvCTj+Gf5hmdpzJnX0Cm83BqEEpmKk0HAXNCmMxQp
+ 3twrjrj/RahXVpnUgQR8PKAn7HjVFs/YvbQtTmFubmllIEJlcm5oYXJkIDxuYW5u
+ aWUuYmVybmhhcmRAZXhhbXBsZS5jb20+iQHUBBMBCgA+FiEExEem9r/Zzvj7NxeF
+ VxYlqTAkEXkFAl+7O0oCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AA
+ CgkQVxYlqTAkEXk9xwv/WlJJGJ+QyGeJAhySG3z3bQnFwb2CusF2LbwcAETDgbkf
+ opkkf34Vbb9A7kM7peZ7Va0Edsg09XdkBUAdaqKQn78HiZJC5n0grXcj1c67Adss
+ Ym9TGVM6AC3K3Vm3wVV0X+ng31rdDpjfIqfYDAvwhMc8H/MHs/dCRSIxEGWK8UKh
+ WLUrX+wN+HNMVbzWPGwoTMWiDa/ofA9INhqN+u+mJkTaP+a4R3LTgL5hp+kUDOaB
+ Nc0rqH7vgj+037NTL8vox18J4qgNbRIsywclMYBJDwfA4w1phtsMu1BKPiOu2kue
+ 18fyGDtboXUPFOJjf5OEwJsu+MFogWeAVuHN/eeiqOAFCYW+TT6Ehc6BnJ8vWCMS
+ Dgs3t6i94gNZtvEty2EAheHEBD1alU4c6S3VENdh5q2KkWIVFxgNtungo03eAVfj
+ UhMjrrEu0LC/Rizo7Me0kG7rfdn9oIwp4MTn7Cst1wGEWdi9UO4NJf1C+P9rFQuG
+ hMaj+8gb1uBdjPG8WOOanQWGBF+7O0oBDADhzNAvjiphKHsa4O5s3BePLQ+DJz+K
+ rS8f9mb66to/w9BlUtnm/L4gVgiIYqGhH7TSDaGhvIDMf3iKKBnKrWeBe0W8cdq3
+ FlzWC/AHUahEFxFm0l6nq0pOIiAVQ58IPaB/0a5YCY7tU2yfw8llZUN8dWJ7cSsB
+ Gpa6Q9/9y4x5/9VPDPduXRv22KCfDbHXuFS79ubmueFfrOa1CLXRhCy3dUXCyePU
+ YuwxixXJRTJQJm+A6c8TFIL+cji7IEzzDAiNexfGzEfu+Qj1/9PzX8aIn6C5Tf4q
+ B1pcGa4uYr8K1aCENcVt6+GA5gMdcplYXmtA212RyPqQmnJIjxDdS7AJYcivqG2q
+ F5CvqzKY5/A+e9+GLyRM36P8LpB8+XHMoYNMNmOl5KX6WZ1tRw/xxgv1iKX3Pcqd
+ noFwsOCNVpTWlxvjsyve8VQUplORSakIhfKh1VWu7j8AKXWe9S3zMYQDq5G8VrTO
+ Vb1pPvPgiNxo9u1OXi2H9UTXhCWYZ6FIe2UAEQEAAf4HAwIlxJFDCl1eRf+8ne6l
+ KpsQfPjhCNnaXE1Q1izRVNGn0gojZkHTRzBF6ZOaPMNSWOri22JoaACI2txuQLyu
+ fHdO+ROr2Pnp17zeXbrm9Tk0PpugPwW/+AkvLPtcSOoCLEzkoKnwKmpC224Ed2Zb
+ Ma5ApPp3HNGkZgPVw5Mvj8R/n8MbKr7/TC7PV9WInranisZqH9fzvA3KEpaDwSr0
+ vBtn6nXzSQKhmwCGRLCUuA+HG2gXIlYuNi7lPpu+Tivz+FnIaTVtrhG5b6Az30QP
+ C0cLe539X9HgryP6M9kzLSYnfpGQMqSqOUYZfhQW6xtSWr7/iWdnYF7S1YouWPLs
+ vuN+xFFKv3eVtErk4UOgAp9it4/i41QuMNwCWCt71278Ugwqygexw/XMi+Rs2Z6C
+ 2ESu1dJnOhYF4eL7ymSKxwBitA+qETQBsjxjegNls/poFjREIhOOwM0w9mn+GptC
+ RVmFdcTlXMGJIGPxTFZQzIitCVoTURrkzBvqUvKFft8GcEBr2izoIqOZU3Npya7c
+ kKHyVMY0n7xjH3Hs4C3A4tBtkbDpwxz+hc9xh5/E/EKKlvZLfIKuuTP4eJap8KEN
+ vvbDPolF3TveTvNLIe86GTSU+wi67PM1PBHKhLSP2aYvS503Z29OLD6Rd6p6jI8u
+ MC8ueF719oH5uG5Sbs3OGmX+UF1aaproLhnGpTwrLyEX7tMebb/JM22Qasj9H9to
+ PNAgEfhlNdhJ+IULkx0My2e55+BIskhsWJpkAhpD2dOyiDBsXZvT3x3dbMKWi1sS
+ +nbKzhMjmUoQ++Vh2uZ9Zi93H3+gsge6e1duRSLNEFrrOk9c6cVPsmle7HoZSzNw
+ qYVCb3npMo+43IgyaK48eGS757ZGsgTEQdicoqVann+wHbAOlWwUFSPTGpqTMMvD
+ 17PVFQB4ADb5J3IAy7kJsVUwoqYI8VrdfiJJUeQikePOi760TCUTJ3PlMUNqngMn
+ ItzNidE8A0RvzFW6DNcPHJVpdGRk36GtWooBhxRwelchAgTSB6gVueF9KTW+EZU2
+ evdAwuTfwvTguOuJ3yJ6g+vFiHYrsczHJXq7QaJbpmJLlavvA2yFPDmlSDMSMKFo
+ t13RwYZ+mPLS5QLK52vbCmDKiQI7Z7zLXIcQ2RXXHQN4OYYLbDXeIMO2BwXAsGJf
+ LC3W64gMUSRKB07UXmDdu4U3US0sqMsxUNWqLFC8PRVR68NAxF+8zS1xKLCUPRWS
+ ELivIY0m4ybzITM6xHBCOSFRph5+LKQVehEo1qM7aoRtS+5SHjdtOeyPEQwSTsWj
+ IWlumHJAXFUmBqc+bVi1m661c5O56VCm7PP61oQQxsB3J0E5OsQUA4kBvAQYAQoA
+ JhYhBMRHpva/2c74+zcXhVcWJakwJBF5BQJfuztKAhsMBQkDwmcAAAoJEFcWJakw
+ JBF5T/ML/3Ml7+493hQuoC9O3HOANkimc0pGxILVeJmJmnfbMDJ71fU84h2+xAyk
+ 2PZc48wVYKju9THJzdRk+XBPO+G6mSBupSt53JIYb5NijotNTmJmHYpG1yb+9FjD
+ EFWTlxK1mr5wjSUxlGWa/O46XjxzCSEUP1SknLWbTOucV8KOmPWL3DupvGINIIQx
+ e5eJ9SMjlHvUn4rq8sd11FT2bQrd+xMx8gP5cearPqB7qVRlHjtOKn29gTV90kIw
+ amRke8KxSoJh+xT057aKI2+MCu7RC8TgThmUVCWgwUzXlsw1Qe8ySc6CmjIBftfo
+ lQYPDSq1u8RSBAB+t2Xwprvdedr9SQihzBk5GCGBJ/npEcgF2jk26sJqoXYbvyQG
+ tqSDQ925oP7OstyOE4FTH7sQmBvP01Ikdgwkm0cthLSpWY4QI+09Aeg+rZ80Etfv
+ vAKquDGA33no8YGnn+epeLqyscIh4WG3bIoHk9JlFCcwIp9U65IfR1fTcvlTdzZN
+ 4f6xMfFu2A==
+ =3YL6
+ -----END PGP PRIVATE KEY BLOCK-----
+ KEY
+ end
+
+ def public_key2
+ <<~KEY.strip
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
+
+ mQGNBF+7O0oBDADvRto4K9PT83Lbyp/qaMPIzBbXHB6ljdDoyb+Pn2UrHk9MhB5v
+ bTgBv+rctOabmimPPalcyaxOQ1GtrYizo1l33YQZupSvaOoStVLWqnBx8eKKcUv8
+ QucS3S2qFhj9G0tdHW7RW2BGrSwEM09d2xFsFKKAj/4RTTU5idYWrvB24DNcrBh+
+ iKsoa+rmJf1bwL6Mn9f9NwzundG16qibY/UwMlltQriWaVMn2AKVuu6HrX9pe3g5
+ Er2Szjc7DZitt6eAy3PmuWHXzDCCvsO7iPxXlywY49hLhDen3/Warwn1pSbp+im4
+ /0oJExLZBSS1xHbRSQoR6matF0+V/6TQz8Yo3g8z9HgyEtn1V7QJo3PoNrnEl73e
+ 9yslTqVtzba0Q132oRoO7eEYf82KrPOmVGj6Q9LpSXFLfsl3GlPgoBxRZXpT62CV
+ 3rGalIa2yKmcBQtyICjR1+PTIAJcVIPyr92xTo4RfLwVFW0czX7LM2H0FT2Ksj7L
+ U450ewBz8N6bFDMAEQEAAbQtTmFubmllIEJlcm5oYXJkIDxuYW5uaWUuYmVybmhh
+ cmRAZXhhbXBsZS5jb20+iQHUBBMBCgA+FiEExEem9r/Zzvj7NxeFVxYlqTAkEXkF
+ Al+7O0oCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQVxYlqTAk
+ EXk9xwv/WlJJGJ+QyGeJAhySG3z3bQnFwb2CusF2LbwcAETDgbkfopkkf34Vbb9A
+ 7kM7peZ7Va0Edsg09XdkBUAdaqKQn78HiZJC5n0grXcj1c67AdssYm9TGVM6AC3K
+ 3Vm3wVV0X+ng31rdDpjfIqfYDAvwhMc8H/MHs/dCRSIxEGWK8UKhWLUrX+wN+HNM
+ VbzWPGwoTMWiDa/ofA9INhqN+u+mJkTaP+a4R3LTgL5hp+kUDOaBNc0rqH7vgj+0
+ 37NTL8vox18J4qgNbRIsywclMYBJDwfA4w1phtsMu1BKPiOu2kue18fyGDtboXUP
+ FOJjf5OEwJsu+MFogWeAVuHN/eeiqOAFCYW+TT6Ehc6BnJ8vWCMSDgs3t6i94gNZ
+ tvEty2EAheHEBD1alU4c6S3VENdh5q2KkWIVFxgNtungo03eAVfjUhMjrrEu0LC/
+ Rizo7Me0kG7rfdn9oIwp4MTn7Cst1wGEWdi9UO4NJf1C+P9rFQuGhMaj+8gb1uBd
+ jPG8WOOauQGNBF+7O0oBDADhzNAvjiphKHsa4O5s3BePLQ+DJz+KrS8f9mb66to/
+ w9BlUtnm/L4gVgiIYqGhH7TSDaGhvIDMf3iKKBnKrWeBe0W8cdq3FlzWC/AHUahE
+ FxFm0l6nq0pOIiAVQ58IPaB/0a5YCY7tU2yfw8llZUN8dWJ7cSsBGpa6Q9/9y4x5
+ /9VPDPduXRv22KCfDbHXuFS79ubmueFfrOa1CLXRhCy3dUXCyePUYuwxixXJRTJQ
+ Jm+A6c8TFIL+cji7IEzzDAiNexfGzEfu+Qj1/9PzX8aIn6C5Tf4qB1pcGa4uYr8K
+ 1aCENcVt6+GA5gMdcplYXmtA212RyPqQmnJIjxDdS7AJYcivqG2qF5CvqzKY5/A+
+ e9+GLyRM36P8LpB8+XHMoYNMNmOl5KX6WZ1tRw/xxgv1iKX3PcqdnoFwsOCNVpTW
+ lxvjsyve8VQUplORSakIhfKh1VWu7j8AKXWe9S3zMYQDq5G8VrTOVb1pPvPgiNxo
+ 9u1OXi2H9UTXhCWYZ6FIe2UAEQEAAYkBvAQYAQoAJhYhBMRHpva/2c74+zcXhVcW
+ JakwJBF5BQJfuztKAhsMBQkDwmcAAAoJEFcWJakwJBF5T/ML/3Ml7+493hQuoC9O
+ 3HOANkimc0pGxILVeJmJmnfbMDJ71fU84h2+xAyk2PZc48wVYKju9THJzdRk+XBP
+ O+G6mSBupSt53JIYb5NijotNTmJmHYpG1yb+9FjDEFWTlxK1mr5wjSUxlGWa/O46
+ XjxzCSEUP1SknLWbTOucV8KOmPWL3DupvGINIIQxe5eJ9SMjlHvUn4rq8sd11FT2
+ bQrd+xMx8gP5cearPqB7qVRlHjtOKn29gTV90kIwamRke8KxSoJh+xT057aKI2+M
+ Cu7RC8TgThmUVCWgwUzXlsw1Qe8ySc6CmjIBftfolQYPDSq1u8RSBAB+t2Xwprvd
+ edr9SQihzBk5GCGBJ/npEcgF2jk26sJqoXYbvyQGtqSDQ925oP7OstyOE4FTH7sQ
+ mBvP01Ikdgwkm0cthLSpWY4QI+09Aeg+rZ80EtfvvAKquDGA33no8YGnn+epeLqy
+ scIh4WG3bIoHk9JlFCcwIp9U65IfR1fTcvlTdzZN4f6xMfFu2A==
+ =RAwd
+ -----END PGP PUBLIC KEY BLOCK-----
+ KEY
+ end
+
+ def fingerprint2
+ 'C447A6F6BFD9CEF8FB371785571625A930241179'
+ end
+
def names
['Nannie Bernhard']
end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index a1b4e6eee92..b20801bd3c4 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -4,6 +4,11 @@ module GraphqlHelpers
MutationDefinition = Struct.new(:query, :variables)
NoData = Class.new(StandardError)
+ UnauthorizedObject = Class.new(StandardError)
+
+ def graphql_args(**values)
+ ::Graphql::Arguments.new(values)
+ end
# makes an underscored string look like a fieldname
# "merge_request" => "mergeRequest"
@@ -17,7 +22,10 @@ module GraphqlHelpers
# ready, then the early return is returned instead.
#
# Then the resolve method is called.
- def resolve(resolver_class, args: {}, **resolver_args)
+ def resolve(resolver_class, args: {}, lookahead: :not_given, parent: :not_given, **resolver_args)
+ args = aliased_args(resolver_class, args)
+ args[:parent] = parent unless parent == :not_given
+ args[:lookahead] = lookahead unless lookahead == :not_given
resolver = resolver_instance(resolver_class, **resolver_args)
ready, early_return = sync_all { resolver.ready?(**args) }
@@ -26,6 +34,16 @@ module GraphqlHelpers
resolver.resolve(**args)
end
+ # TODO: Remove this method entirely when GraphqlHelpers uses real resolve_field
+ # see: https://gitlab.com/gitlab-org/gitlab/-/issues/287791
+ def aliased_args(resolver, args)
+ definitions = resolver.arguments
+
+ args.transform_keys do |k|
+ definitions[GraphqlHelpers.fieldnamerize(k)]&.keyword || k
+ end
+ end
+
def resolver_instance(resolver_class, obj: nil, ctx: {}, field: nil, schema: GitlabSchema)
if ctx.is_a?(Hash)
q = double('Query', schema: schema)
@@ -111,24 +129,33 @@ module GraphqlHelpers
def variables_for_mutation(name, input)
graphql_input = prepare_input_for_mutation(input)
- result = { input_variable_name_for_mutation(name) => graphql_input }
+ { input_variable_name_for_mutation(name) => graphql_input }
+ end
- # Avoid trying to serialize multipart data into JSON
- if graphql_input.values.none? { |value| io_value?(value) }
- result.to_json
- else
- result
- end
+ def serialize_variables(variables)
+ return unless variables
+ return variables if variables.is_a?(String)
+
+ ::Gitlab::Utils::MergeHash.merge(Array.wrap(variables).map(&:to_h)).to_json
end
- def resolve_field(name, object, args = {})
- context = double("Context",
- schema: GitlabSchema,
- query: GraphQL::Query.new(GitlabSchema),
- parent: nil)
- field = described_class.fields[name]
+ def resolve_field(name, object, args = {}, current_user: nil)
+ q = GraphQL::Query.new(GitlabSchema)
+ context = GraphQL::Query::Context.new(query: q, object: object, values: { current_user: current_user })
+ allow(context).to receive(:parent).and_return(nil)
+ field = described_class.fields.fetch(GraphqlHelpers.fieldnamerize(name))
instance = described_class.authorized_new(object, context)
- field.resolve_field(instance, {}, context)
+ raise UnauthorizedObject unless instance
+
+ field.resolve_field(instance, args, context)
+ end
+
+ def simple_resolver(resolved_value = 'Resolved value')
+ Class.new(Resolvers::BaseResolver) do
+ define_method :resolve do |**_args|
+ resolved_value
+ end
+ end
end
# Recursively convert a Hash with Ruby-style keys to GraphQL fieldname-style keys
@@ -165,10 +192,32 @@ module GraphqlHelpers
end
def query_graphql_field(name, attributes = {}, fields = nil)
- <<~QUERY
- #{field_with_params(name, attributes)}
- #{wrap_fields(fields || all_graphql_fields_for(name.to_s.classify))}
- QUERY
+ attributes, fields = [nil, attributes] if fields.nil? && !attributes.is_a?(Hash)
+
+ field = field_with_params(name, attributes)
+
+ field + wrap_fields(fields || all_graphql_fields_for(name.to_s.classify)).to_s
+ end
+
+ def page_info_selection
+ "pageInfo { hasNextPage hasPreviousPage endCursor startCursor }"
+ end
+
+ def query_nodes(name, fields = nil, args: nil, of: name, include_pagination_info: false, max_depth: 1)
+ fields ||= all_graphql_fields_for(of.to_s.classify, max_depth: max_depth)
+ node_selection = include_pagination_info ? "#{page_info_selection} nodes" : :nodes
+ query_graphql_path([[name, args], node_selection], fields)
+ end
+
+ # e.g:
+ # query_graphql_path(%i[foo bar baz], all_graphql_fields_for('Baz'))
+ # => foo { bar { baz { x y z } } }
+ def query_graphql_path(segments, fields = nil)
+ # we really want foldr here...
+ segments.reverse.reduce(fields) do |tail, segment|
+ name, args = Array.wrap(segment)
+ query_graphql_field(name, args, tail)
+ end
end
def wrap_fields(fields)
@@ -203,50 +252,22 @@ module GraphqlHelpers
type = GitlabSchema.types[class_name.to_s]
return "" unless type
- type.fields.map do |name, field|
- # We can't guess arguments, so skip fields that require them
- next if required_arguments?(field)
- next if excluded.include?(name)
-
- singular_field_type = field_type(field)
-
- # If field type is the same as parent type, then we're hitting into
- # mutual dependency. Break it from infinite recursion
- next if parent_types.include?(singular_field_type)
+ # We can't guess arguments, so skip fields that require them
+ skip = ->(name, field) { excluded.include?(name) || required_arguments?(field) }
- if nested_fields?(field)
- fields =
- all_graphql_fields_for(singular_field_type, parent_types | [type], max_depth: max_depth - 1)
-
- "#{name} { #{fields} }" unless fields.blank?
- else
- name
- end
- end.compact.join("\n")
+ ::Graphql::FieldSelection.select_fields(type, skip, parent_types, max_depth)
end
- def attributes_to_graphql(attributes)
- attributes.map do |name, value|
- value_str = as_graphql_literal(value)
+ def with_signature(variables, query)
+ %Q[query(#{variables.map(&:sig).join(', ')}) #{query}]
+ end
- "#{GraphqlHelpers.fieldnamerize(name.to_s)}: #{value_str}"
- end.join(", ")
+ def var(type)
+ ::Graphql::Var.new(generate(:variable), type)
end
- # Fairly dumb Ruby => GraphQL rendering function. Only suitable for testing.
- # Use symbol for Enum values
- def as_graphql_literal(value)
- case value
- when Array then "[#{value.map { |v| as_graphql_literal(v) }.join(',')}]"
- when Hash then "{#{attributes_to_graphql(value)}}"
- when Integer, Float then value.to_s
- when String then "\"#{value.gsub(/"/, '\\"')}\""
- when Symbol then value
- when nil then 'null'
- when true then 'true'
- when false then 'false'
- else raise ArgumentError, "Cannot represent #{value} as GraphQL literal"
- end
+ def attributes_to_graphql(arguments)
+ ::Graphql::Arguments.new(arguments).to_s
end
def post_multiplex(queries, current_user: nil, headers: {})
@@ -254,7 +275,7 @@ module GraphqlHelpers
end
def post_graphql(query, current_user: nil, variables: nil, headers: {})
- params = { query: query, variables: variables&.to_json }
+ params = { query: query, variables: serialize_variables(variables) }
post api('/', current_user, version: 'graphql'), params: params, headers: headers
end
@@ -320,36 +341,47 @@ module GraphqlHelpers
{ operations: operations.to_json, map: map.to_json }.merge(extracted_files)
end
+ def fresh_response_data
+ Gitlab::Json.parse(response.body)
+ end
+
# Raises an error if no data is found
- def graphql_data
+ def graphql_data(body = json_response)
# Note that `json_response` is defined as `let(:json_response)` and
# therefore, in a spec with multiple queries, will only contain data
# from the _first_ query, not subsequent ones
- json_response['data'] || (raise NoData, graphql_errors)
+ body['data'] || (raise NoData, graphql_errors(body))
end
def graphql_data_at(*path)
graphql_dig_at(graphql_data, *path)
end
+ # Slightly more powerful than just `dig`:
+ # - also supports implicit flat-mapping (.e.g. :foo :nodes :bar :nodes)
def graphql_dig_at(data, *path)
keys = path.map { |segment| segment.is_a?(Integer) ? segment : GraphqlHelpers.fieldnamerize(segment) }
# Allows for array indexing, like this
# ['project', 'boards', 'edges', 0, 'node', 'lists']
keys.reduce(data) do |memo, key|
- memo.is_a?(Array) ? memo[key] : memo&.dig(key)
+ if memo.is_a?(Array)
+ key.is_a?(Integer) ? memo[key] : memo.flat_map { |e| Array.wrap(e[key]) }
+ else
+ memo&.dig(key)
+ end
end
end
- def graphql_errors
- case json_response
+ # See note at graphql_data about memoization and multiple requests
+ def graphql_errors(body = json_response)
+ case body
when Hash # regular query
- json_response['errors']
+ body['errors']
when Array # multiplexed queries
- json_response.map { |response| response['errors'] }
+ body.map { |response| response['errors'] }
else
- raise "Unknown GraphQL response type #{json_response.class}"
+ raise "Unknown GraphQL response type #{body.class}"
end
end
@@ -392,19 +424,29 @@ module GraphqlHelpers
end
def nested_fields?(field)
- !scalar?(field) && !enum?(field)
+ ::Graphql::FieldInspection.new(field).nested_fields?
end
def scalar?(field)
- field_type(field).kind.scalar?
+ ::Graphql::FieldInspection.new(field).scalar?
end
def enum?(field)
- field_type(field).kind.enum?
+ ::Graphql::FieldInspection.new(field).enum?
end
+ # There are a few non BaseField fields in our schema (pageInfo for one).
+ # None of them require arguments.
def required_arguments?(field)
- field.arguments.values.any? { |argument| argument.type.non_null? }
+ return field.requires_argument? if field.is_a?(::Types::BaseField)
+
+ if (meta = field.try(:metadata)) && meta[:type_class]
+ required_arguments?(meta[:type_class])
+ elsif args = field.try(:arguments)
+ args.values.any? { |argument| argument.type.non_null? }
+ else
+ false
+ end
end
def io_value?(value)
@@ -412,15 +454,7 @@ module GraphqlHelpers
end
def field_type(field)
- field_type = field.type.respond_to?(:to_graphql) ? field.type.to_graphql : field.type
-
- # The type could be nested. For example `[GraphQL::STRING_TYPE]`:
- # - List
- # - String!
- # - String
- field_type = field_type.of_type while field_type.respond_to?(:of_type)
-
- field_type
+ ::Graphql::FieldInspection.new(field).type
end
# for most tests, we want to allow unlimited complexity
@@ -498,6 +532,20 @@ module GraphqlHelpers
variables: {}
)
end
+
+ # A lookahead that selects everything
+ def positive_lookahead
+ double(selects?: true).tap do |selection|
+ allow(selection).to receive(:selection).and_return(selection)
+ end
+ end
+
+ # A lookahead that selects nothing
+ def negative_lookahead
+ double(selects?: false).tap do |selection|
+ allow(selection).to receive(:selection).and_return(selection)
+ end
+ end
end
# This warms our schema, doing this as part of loading the helpers to avoid
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index e21d4497cda..86022e16d71 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -138,13 +138,15 @@ module LoginHelpers
secret: 'mock_secret'
},
extra: {
- raw_info: {
- info: {
- name: 'mockuser',
- email: email,
- image: 'mock_user_thumbnail_url'
+ raw_info: OneLogin::RubySaml::Attributes.new(
+ {
+ info: {
+ name: 'mockuser',
+ email: email,
+ image: 'mock_user_thumbnail_url'
+ }
}
- },
+ ),
response_object: response_object
}
}).merge(additional_info) { |_, old_hash, new_hash| old_hash.merge(new_hash) }
@@ -198,7 +200,7 @@ module LoginHelpers
env['omniauth.error.strategy'] = strategy
end
- def stub_omniauth_saml_config(messages, context: Rails.application)
+ def stub_omniauth_saml_config(context: Rails.application, **messages)
set_devise_mapping(context: context)
routes = Rails.application.routes
routes.disable_clear_and_finalize = true
diff --git a/spec/support/helpers/multipart_helpers.rb b/spec/support/helpers/multipart_helpers.rb
index 2e8db0e9e42..bcb184f84c5 100644
--- a/spec/support/helpers/multipart_helpers.rb
+++ b/spec/support/helpers/multipart_helpers.rb
@@ -37,7 +37,7 @@ module MultipartHelpers
# *without* the "#{key}." prefix
result.deep_transform_keys! { |k| k.remove("#{key}.") }
{
- "#{key}.gitlab-workhorse-upload" => jwt_token('upload' => result)
+ "#{key}.gitlab-workhorse-upload" => jwt_token(data: { 'upload' => result })
}
end
diff --git a/spec/support/helpers/next_instance_of.rb b/spec/support/helpers/next_instance_of.rb
index 83c788c3d38..a8e9ab2bafe 100644
--- a/spec/support/helpers/next_instance_of.rb
+++ b/spec/support/helpers/next_instance_of.rb
@@ -1,28 +1,31 @@
# frozen_string_literal: true
module NextInstanceOf
- def expect_next_instance_of(klass, *new_args)
- stub_new(expect(klass), *new_args) do |expectation|
- yield(expectation)
- end
+ def expect_next_instance_of(klass, *new_args, &blk)
+ stub_new(expect(klass), nil, *new_args, &blk)
end
- def allow_next_instance_of(klass, *new_args)
- stub_new(allow(klass), *new_args) do |allowance|
- yield(allowance)
- end
+ def expect_next_instances_of(klass, number, *new_args, &blk)
+ stub_new(expect(klass), number, *new_args, &blk)
+ end
+
+ def allow_next_instance_of(klass, *new_args, &blk)
+ stub_new(allow(klass), nil, *new_args, &blk)
+ end
+
+ def allow_next_instances_of(klass, number, *new_args, &blk)
+ stub_new(allow(klass), number, *new_args, &blk)
end
private
- def stub_new(target, *new_args)
+ def stub_new(target, number, *new_args, &blk)
receive_new = receive(:new)
+ receive_new.exactly(number).times if number
receive_new.with(*new_args) if new_args.any?
target.to receive_new.and_wrap_original do |method, *original_args|
- method.call(*original_args).tap do |instance|
- yield(instance)
- end
+ method.call(*original_args).tap(&blk)
end
end
end
diff --git a/spec/support/helpers/services_helper.rb b/spec/support/helpers/services_helper.rb
new file mode 100644
index 00000000000..bf007815551
--- /dev/null
+++ b/spec/support/helpers/services_helper.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require_relative './after_next_helpers'
+
+module ServicesHelper
+ include AfterNextHelpers
+
+ def expect_execution_of(service_class, *args)
+ expect_next(service_class, *args).to receive(:execute)
+ end
+end
diff --git a/spec/support/helpers/snowplow_helpers.rb b/spec/support/helpers/snowplow_helpers.rb
index 15eac1b24fc..70a4eadd8de 100644
--- a/spec/support/helpers/snowplow_helpers.rb
+++ b/spec/support/helpers/snowplow_helpers.rb
@@ -31,7 +31,31 @@ module SnowplowHelpers
# )
# end
# end
- def expect_snowplow_event(category:, action:, **kwargs)
+ #
+ # Passing context:
+ #
+ # Simply provide a hash that has the schema and data expected.
+ #
+ # expect_snowplow_event(
+ # category: 'Experiment',
+ # action: 'created',
+ # context: [
+ # {
+ # schema: 'iglu:com.gitlab/.../0-3-0',
+ # data: { key: 'value' }
+ # }
+ # ]
+ # )
+ def expect_snowplow_event(category:, action:, context: nil, **kwargs)
+ if context
+ kwargs[:context] = []
+ context.each do |c|
+ expect(SnowplowTracker::SelfDescribingJson).to have_received(:new)
+ .with(c[:schema], c[:data]).at_least(:once)
+ kwargs[:context] << an_instance_of(SnowplowTracker::SelfDescribingJson)
+ end
+ end
+
expect(Gitlab::Tracking).to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
.with(category, action, **kwargs).at_least(:once)
end
diff --git a/spec/support/helpers/stub_experiments.rb b/spec/support/helpers/stub_experiments.rb
index 7a6154d5ef9..247692d83ee 100644
--- a/spec/support/helpers/stub_experiments.rb
+++ b/spec/support/helpers/stub_experiments.rb
@@ -3,15 +3,15 @@
module StubExperiments
# Stub Experiment with `key: true/false`
#
- # @param [Hash] experiment where key is feature name and value is boolean whether enabled or not.
+ # @param [Hash] experiment where key is feature name and value is boolean whether active or not.
#
# Examples
- # - `stub_experiment(signup_flow: false)` ... Disable `signup_flow` experiment globally.
+ # - `stub_experiment(signup_flow: false)` ... Disables `signup_flow` experiment.
def stub_experiment(experiments)
- allow(Gitlab::Experimentation).to receive(:enabled?).and_call_original
+ allow(Gitlab::Experimentation).to receive(:active?).and_call_original
experiments.each do |experiment_key, enabled|
- allow(Gitlab::Experimentation).to receive(:enabled?).with(experiment_key) { enabled }
+ allow(Gitlab::Experimentation).to receive(:active?).with(experiment_key) { enabled }
end
end
@@ -20,12 +20,12 @@ module StubExperiments
# @param [Hash] experiment where key is feature name and value is boolean whether enabled or not.
#
# Examples
- # - `stub_experiment_for_user(signup_flow: false)` ... Disable `signup_flow` experiment for user.
- def stub_experiment_for_user(experiments)
- allow(Gitlab::Experimentation).to receive(:enabled_for_value?).and_call_original
+ # - `stub_experiment_for_subject(signup_flow: false)` ... Disable `signup_flow` experiment for user.
+ def stub_experiment_for_subject(experiments)
+ allow(Gitlab::Experimentation).to receive(:in_experiment_group?).and_call_original
experiments.each do |experiment_key, enabled|
- allow(Gitlab::Experimentation).to receive(:enabled_for_value?).with(experiment_key, anything) { enabled }
+ allow(Gitlab::Experimentation).to receive(:in_experiment_group?).with(experiment_key, anything) { enabled }
end
end
end
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index dba3d2b137e..dc54a21d0fa 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -22,6 +22,16 @@ module StubObjectStorage
background_upload: false,
direct_upload: false
)
+ new_config = config.to_h.deep_symbolize_keys.merge({
+ enabled: enabled,
+ proxy_download: proxy_download,
+ background_upload: background_upload,
+ direct_upload: direct_upload
+ })
+
+ # Needed for ObjectStorage::Config compatibility
+ allow(config).to receive(:to_hash).and_return(new_config)
+ allow(config).to receive(:to_h).and_return(new_config)
allow(config).to receive(:enabled) { enabled }
allow(config).to receive(:proxy_download) { proxy_download }
allow(config).to receive(:background_upload) { background_upload }
@@ -84,13 +94,6 @@ module StubObjectStorage
def stub_terraform_state_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store,
- uploader: Terraform::VersionedStateUploader,
- remote_directory: 'terraform',
- **params)
- end
-
- def stub_terraform_state_version_object_storage(**params)
- stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store,
uploader: Terraform::StateUploader,
remote_directory: 'terraform',
**params)
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 4c78ca0117c..01571277a1d 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -168,6 +168,11 @@ module TestEnv
version: Gitlab::GitalyClient.expected_server_version,
task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
Gitlab::SetupHelper::Gitaly.create_configuration(gitaly_dir, { 'default' => repos_path }, force: true)
+ Gitlab::SetupHelper::Gitaly.create_configuration(
+ gitaly_dir,
+ { 'default' => repos_path }, force: true,
+ options: { gitaly_socket: "gitaly2.socket", config_filename: "gitaly2.config.toml" }
+ )
Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true)
end
@@ -241,15 +246,38 @@ module TestEnv
end
def setup_workhorse
- install_workhorse_args = [workhorse_dir, workhorse_url].compact.join(',')
-
- component_timed_setup(
- 'GitLab Workhorse',
- install_dir: workhorse_dir,
- version: Gitlab::Workhorse.version,
- task: "gitlab:workhorse:install[#{install_workhorse_args}]") do
- Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil)
- end
+ start = Time.now
+ return if skip_compile_workhorse?
+
+ puts "\n==> Setting up GitLab Workhorse..."
+
+ FileUtils.rm_rf(workhorse_dir)
+ Gitlab::SetupHelper::Workhorse.compile_into(workhorse_dir)
+ Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil)
+
+ File.write(workhorse_tree_file, workhorse_tree) if workhorse_source_clean?
+
+ puts " GitLab Workhorse set up in #{Time.now - start} seconds...\n"
+ end
+
+ def skip_compile_workhorse?
+ File.directory?(workhorse_dir) &&
+ workhorse_source_clean? &&
+ File.exist?(workhorse_tree_file) &&
+ workhorse_tree == File.read(workhorse_tree_file)
+ end
+
+ def workhorse_source_clean?
+ out = IO.popen(%w[git status --porcelain workhorse], &:read)
+ $?.success? && out.empty?
+ end
+
+ def workhorse_tree
+ IO.popen(%w[git rev-parse HEAD:workhorse], &:read)
+ end
+
+ def workhorse_tree_file
+ File.join(workhorse_dir, 'WORKHORSE_TREE')
end
def workhorse_dir
@@ -260,7 +288,7 @@ module TestEnv
host = "[#{host}]" if host.include?(':')
listen_addr = [host, port].join(':')
- config_path = Gitlab::SetupHelper::Workhorse.get_config_path(workhorse_dir)
+ config_path = Gitlab::SetupHelper::Workhorse.get_config_path(workhorse_dir, {})
# This should be set up in setup_workhorse, but since
# component_needs_update? only checks that versions are consistent,
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index 8e8aeea2ea1..df79049123d 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -85,6 +85,7 @@ module UsageDataHelpers
projects
projects_imported_from_github
projects_asana_active
+ projects_jenkins_active
projects_jira_active
projects_jira_server_active
projects_jira_cloud_active
@@ -161,7 +162,6 @@ module UsageDataHelpers
git
gitaly
database
- avg_cycle_analytics
prometheus_metrics_enabled
web_ide_clientside_preview_enabled
ingress_modsecurity_enabled
diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb
index 7e95f49aea2..cd8387de686 100644
--- a/spec/support/helpers/workhorse_helpers.rb
+++ b/spec/support/helpers/workhorse_helpers.rb
@@ -85,15 +85,15 @@ module WorkhorseHelpers
return {} if upload_params.empty?
- { "#{key}.gitlab-workhorse-upload" => jwt_token('upload' => upload_params) }
+ { "#{key}.gitlab-workhorse-upload" => jwt_token(data: { 'upload' => upload_params }) }
end
- def jwt_token(data = {}, issuer: 'gitlab-workhorse', secret: Gitlab::Workhorse.secret, algorithm: 'HS256')
+ def jwt_token(data: {}, issuer: 'gitlab-workhorse', secret: Gitlab::Workhorse.secret, algorithm: 'HS256')
JWT.encode({ 'iss' => issuer }.merge(data), secret, algorithm)
end
def workhorse_rewritten_fields_header(fields)
- { Gitlab::Middleware::Multipart::RACK_ENV_KEY => jwt_token('rewritten_fields' => fields) }
+ { Gitlab::Middleware::Multipart::RACK_ENV_KEY => jwt_token(data: { 'rewritten_fields' => fields }) }
end
def workhorse_disk_accelerated_file_params(key, file)
diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb
index ae951ea35af..a6b395ad4d5 100644
--- a/spec/support/import_export/common_util.rb
+++ b/spec/support/import_export/common_util.rb
@@ -70,9 +70,12 @@ module ImportExport
)
end
- def get_shared_env(path:)
+ def get_shared_env(path:, logger: nil)
+ logger ||= double(info: true, warn: true, error: true)
+
instance_double(Gitlab::ImportExport::Shared).tap do |shared|
allow(shared).to receive(:export_path).and_return(path)
+ allow(shared).to receive(:logger).and_return(logger)
end
end
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
index c9ff777f604..acf5fb0944f 100644
--- a/spec/support/matchers/access_matchers.rb
+++ b/spec/support/matchers/access_matchers.rb
@@ -52,7 +52,7 @@ module AccessMatchers
emulate_user(user, @membership)
visit(url)
- status_code == 200 && current_path != new_user_session_path
+ status_code == 200 && !current_path.in?([new_user_session_path, new_admin_session_path])
end
chain :of do |membership|
@@ -67,7 +67,7 @@ module AccessMatchers
emulate_user(user, @membership)
visit(url)
- [401, 404].include?(status_code) || current_path == new_user_session_path
+ [401, 404, 403].include?(status_code) || current_path.in?([new_user_session_path, new_admin_session_path])
end
chain :of do |membership|
diff --git a/spec/support/matchers/be_valid_json.rb b/spec/support/matchers/be_valid_json.rb
new file mode 100644
index 00000000000..f46c35c7198
--- /dev/null
+++ b/spec/support/matchers/be_valid_json.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :be_valid_json do
+ def according_to_schema(schema)
+ @schema = schema
+ self
+ end
+
+ match do |actual|
+ data = Gitlab::Json.parse(actual)
+
+ if @schema.present?
+ @validation_errors = JSON::Validator.fully_validate(@schema, data)
+ @validation_errors.empty?
+ else
+ data.present?
+ end
+ rescue JSON::ParserError => e
+ @error = e
+ false
+ end
+
+ def failure_message
+ if @error
+ "Parse failed with error: #{@error}"
+ elsif @validation_errors.present?
+ "Validation failed because #{@validation_errors.join(', and ')}"
+ else
+ "Parsing did not return any data"
+ end
+ end
+end
diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb
index 04482d3bfb8..7a66eff3a41 100644
--- a/spec/support/matchers/exceed_query_limit.rb
+++ b/spec/support/matchers/exceed_query_limit.rb
@@ -3,6 +3,13 @@
module ExceedQueryLimitHelpers
MARGINALIA_ANNOTATION_REGEX = %r{\s*\/\*.*\*\/}.freeze
+ DB_QUERY_RE = Regexp.union([
+ /^(?<prefix>SELECT .* FROM "?[a-z_]+"?) (?<suffix>.*)$/m,
+ /^(?<prefix>UPDATE "?[a-z_]+"?) (?<suffix>.*)$/m,
+ /^(?<prefix>INSERT INTO "[a-z_]+" \((?:"[a-z_]+",?\s?)+\)) (?<suffix>.*)$/m,
+ /^(?<prefix>DELETE FROM "[a-z_]+") (?<suffix>.*)$/m
+ ]).freeze
+
def with_threshold(threshold)
@threshold = threshold
self
@@ -13,41 +20,129 @@ module ExceedQueryLimitHelpers
self
end
+ def show_common_queries
+ @show_common_queries = true
+ self
+ end
+
+ def ignoring(pattern)
+ @ignoring_pattern = pattern
+ self
+ end
+
def threshold
@threshold.to_i
end
def expected_count
if expected.is_a?(ActiveRecord::QueryRecorder)
- expected.count
+ query_recorder_count(expected)
else
expected
end
end
def actual_count
- @actual_count ||= if @query
- recorder.log.select { |recorded| recorded =~ @query }.size
- else
- recorder.count
- end
+ @actual_count ||= query_recorder_count(recorder)
+ end
+
+ def query_recorder_count(query_recorder)
+ return query_recorder.count unless @query || @ignoring_pattern
+
+ query_log(query_recorder).size
+ end
+
+ def query_log(query_recorder)
+ filtered = query_recorder.log
+ filtered = filtered.select { |q| q =~ @query } if @query
+ filtered = filtered.reject { |q| q =~ @ignoring_pattern } if @ignoring_pattern
+ filtered
end
def recorder
@recorder ||= ActiveRecord::QueryRecorder.new(skip_cached: skip_cached, &@subject_block)
end
- def count_queries(queries)
- queries.each_with_object(Hash.new(0)) { |query, counts| counts[query] += 1 }
+ # Take a query recorder and tabulate the frequencies of suffixes for each prefix.
+ #
+ # @return Hash[String, Hash[String, Int]]
+ #
+ # Example:
+ #
+ # r = ActiveRecord::QueryRecorder.new do
+ # SomeTable.create(x: 1, y: 2, z: 3)
+ # SomeOtherTable.where(id: 1).first
+ # SomeTable.create(x: 4, y: 5, z: 6)
+ # SomeOtherTable.all
+ # end
+ # count_queries(r)
+ # #=>
+ # {
+ # 'INSERT INTO "some_table" VALUES' => {
+ # '(1,2,3)' => 1,
+ # '(4,5,6)' => 1
+ # },
+ # 'SELECT * FROM "some_other_table"' => {
+ # 'WHERE id = 1 LIMIT 1' => 1,
+ # '' => 2
+ # }
+ # }
+ def count_queries(query_recorder)
+ strip_marginalia_annotations(query_log(query_recorder))
+ .map { |q| query_group_key(q) }
+ .group_by { |k| k[:prefix] }
+ .transform_values { |keys| frequencies(:suffix, keys) }
+ end
+
+ def frequencies(key, things)
+ things.group_by { |x| x[key] }.transform_values(&:size)
+ end
+
+ def query_group_key(query)
+ DB_QUERY_RE.match(query) || { prefix: query, suffix: '' }
+ end
+
+ def diff_query_counts(expected, actual)
+ expected_counts = expected.transform_values do |suffixes|
+ suffixes.transform_values { |n| [n, 0] }
+ end
+ recorded_counts = actual.transform_values do |suffixes|
+ suffixes.transform_values { |n| [0, n] }
+ end
+
+ combined_counts = expected_counts.merge(recorded_counts) do |_k, exp, got|
+ exp.merge(got) do |_k, exp_counts, got_counts|
+ exp_counts.zip(got_counts).map { |a, b| a + b }
+ end
+ end
+
+ unless @show_common_queries
+ combined_counts = combined_counts.transform_values do |suffs|
+ suffs.reject { |_k, counts| counts.first == counts.second }
+ end
+ end
+
+ combined_counts.reject { |_prefix, suffs| suffs.empty? }
+ end
+
+ def diff_query_group_message(query, suffixes)
+ suffix_messages = suffixes.map do |s, counts|
+ "-- (expected: #{counts.first}, got: #{counts.second})\n #{s}"
+ end
+
+ "#{query}...\n#{suffix_messages.join("\n")}"
end
def log_message
if expected.is_a?(ActiveRecord::QueryRecorder)
- counts = count_queries(strip_marginalia_annotations(expected.log))
- extra_queries = strip_marginalia_annotations(@recorder.log).reject { |query| counts[query] -= 1 unless counts[query] == 0 }
- extra_queries_display = count_queries(extra_queries).map { |query, count| "[#{count}] #{query}" }
-
- (['Extra queries:'] + extra_queries_display).join("\n\n")
+ diff_counts = diff_query_counts(count_queries(expected), count_queries(@recorder))
+ sections = diff_counts.map { |q, suffixes| diff_query_group_message(q, suffixes) }
+
+ <<~MSG
+ Query Diff:
+ -----------
+ #{sections.join("\n\n")}
+ MSG
else
@recorder.log_message
end
diff --git a/spec/support/services/issuable_import_csv_service_shared_examples.rb b/spec/support/services/issuable_import_csv_service_shared_examples.rb
index 20ac2ff5c7c..f68750bec32 100644
--- a/spec/support/services/issuable_import_csv_service_shared_examples.rb
+++ b/spec/support/services/issuable_import_csv_service_shared_examples.rb
@@ -26,29 +26,33 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type|
end
end
+ shared_examples_for 'invalid file' do
+ it 'returns invalid file error' do
+ expect(subject[:success]).to eq(0)
+ expect(subject[:parse_error]).to eq(true)
+ end
+
+ it_behaves_like 'importer with email notification'
+ it_behaves_like 'an issuable importer'
+ end
+
describe '#execute' do
- context 'invalid file' do
+ context 'invalid file extension' do
let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }
- it 'returns invalid file error' do
- expect(subject[:success]).to eq(0)
- expect(subject[:parse_error]).to eq(true)
- end
+ it_behaves_like 'invalid file'
+ end
- it_behaves_like 'importer with email notification'
- it_behaves_like 'an issuable importer'
+ context 'empty file' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_empty.csv') }
+
+ it_behaves_like 'invalid file'
end
context 'file without headers' do
let(:file) { fixture_file_upload('spec/fixtures/csv_no_headers.csv') }
- it 'returns invalid file error' do
- expect(subject[:success]).to eq(0)
- expect(subject[:parse_error]).to eq(true)
- end
-
- it_behaves_like 'importer with email notification'
- it_behaves_like 'an issuable importer'
+ it_behaves_like 'invalid file'
end
context 'with a file generated by Gitlab CSV export' do
diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
index 68ff16922d8..6b15eadc1c1 100644
--- a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
@@ -9,13 +9,13 @@ RSpec.shared_context 'GroupProjectsFinder context' do
let(:finder) { described_class.new(group: group, current_user: current_user, params: params, options: options) }
- let_it_be(:public_project) { create(:project, :public, group: group, path: '1') }
- let_it_be(:private_project) { create(:project, :private, group: group, path: '2') }
- let_it_be(:shared_project_1) { create(:project, :public, path: '3') }
- let_it_be(:shared_project_2) { create(:project, :private, path: '4') }
- let_it_be(:shared_project_3) { create(:project, :internal, path: '5') }
- let_it_be(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) }
- let_it_be(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) }
+ let_it_be(:public_project) { create(:project, :public, group: group, path: '1', name: 'g') }
+ let_it_be(:private_project) { create(:project, :private, group: group, path: '2', name: 'f') }
+ let_it_be(:shared_project_1) { create(:project, :public, path: '3', name: 'e') }
+ let_it_be(:shared_project_2) { create(:project, :private, path: '4', name: 'd') }
+ let_it_be(:shared_project_3) { create(:project, :internal, path: '5', name: 'c') }
+ let_it_be(:subgroup_project) { create(:project, :public, path: '6', group: subgroup, name: 'b') }
+ let_it_be(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup, name: 'a') }
before do
shared_project_1.project_group_links.create!(group_access: Gitlab::Access::MAINTAINER, group: group)
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
index 88c31bf9cfd..4c003dff947 100644
--- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -54,19 +54,19 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
let!(:label2) { create(:label, project: project1) }
let!(:merge_request1) do
- create(:merge_request, assignees: [user], author: user,
+ create(:merge_request, assignees: [user], author: user, reviewers: [user2],
source_project: project2, target_project: project1,
target_branch: 'merged-target')
end
let!(:merge_request2) do
- create(:merge_request, :conflict, assignees: [user], author: user,
+ create(:merge_request, :conflict, assignees: [user], author: user, reviewers: [user2],
source_project: project2, target_project: project1,
state: 'closed')
end
let!(:merge_request3) do
- create(:merge_request, :simple, author: user, assignees: [user2],
+ create(:merge_request, :simple, author: user, assignees: [user2], reviewers: [user],
source_project: project2, target_project: project2,
state: 'locked',
title: 'thing WIP thing')
diff --git a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
index 79fc42e73c7..0fee170a35d 100644
--- a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
+++ b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
@@ -1,8 +1,35 @@
# frozen_string_literal: true
-RSpec.shared_context 'merge request show action' do
+RSpec.shared_context 'open merge request show action' do
+ include Spec::Support::Helpers::Features::MergeRequestHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:note) { create(:note_on_merge_request, project: project, noteable: open_merge_request) }
+
+ let(:open_merge_request) do
+ create(:merge_request, :opened, source_project: project, author: user)
+ end
+
+ before do
+ assign(:project, project)
+ assign(:merge_request, open_merge_request)
+ assign(:note, note)
+ assign(:noteable, open_merge_request)
+ assign(:notes, [])
+ assign(:pipelines, Ci::Pipeline.none)
+ assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, open_merge_request))
+
+ preload_view_requirements(open_merge_request, note)
+
+ sign_in(user)
+ end
+end
+
+RSpec.shared_context 'closed merge request show action' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
+ include Spec::Support::Helpers::Features::MergeRequestHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
@@ -17,13 +44,6 @@ RSpec.shared_context 'merge request show action' do
author: user)
end
- def preload_view_requirements
- # This will load the status fields of the author of the note and merge request
- # to avoid queries in when rendering the view being tested.
- closed_merge_request.author.status
- note.author.status
- end
-
before do
assign(:project, project)
assign(:merge_request, closed_merge_request)
@@ -34,16 +54,10 @@ RSpec.shared_context 'merge request show action' do
assign(:pipelines, Ci::Pipeline.none)
assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request))
- preload_view_requirements
+ preload_view_requirements(closed_merge_request, note)
allow(view).to receive_messages(current_user: user,
can?: true,
current_application_settings: Gitlab::CurrentSettings.current_application_settings)
end
-
- def serialize_issuable_sidebar(user, project, merge_request)
- MergeRequestSerializer
- .new(current_user: user, project: project)
- .represent(closed_merge_request, serializer: 'sidebar')
- end
end
diff --git a/spec/support/shared_contexts/lib/gitlab/middleware/with_a_mocked_gitlab_instance_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/middleware/with_a_mocked_gitlab_instance_shared_context.rb
index 3830b89f1ff..5d65c168d8d 100644
--- a/spec/support/shared_contexts/lib/gitlab/middleware/with_a_mocked_gitlab_instance_shared_context.rb
+++ b/spec/support/shared_contexts/lib/gitlab/middleware/with_a_mocked_gitlab_instance_shared_context.rb
@@ -25,7 +25,7 @@ RSpec.shared_context 'with a mocked GitLab instance' do
let(:request) { Rack::MockRequest.new(rack_stack) }
subject do
- described_class.new(fake_app).tap do |app|
+ Gitlab::Middleware::ReadOnly.new(fake_app).tap do |app|
app.extend(observe_env)
end
end
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index ed74c3f179f..549dc1cff1d 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -14,6 +14,15 @@ RSpec.shared_context 'project navbar structure' do
}
end
+ let(:security_and_compliance_nav_item) do
+ {
+ nav_item: _('Security & Compliance'),
+ nav_sub_items: [
+ _('Audit Events')
+ ]
+ }
+ end
+
let(:structure) do
[
{
@@ -62,6 +71,7 @@ RSpec.shared_context 'project navbar structure' do
_('Schedules')
]
},
+ (security_and_compliance_nav_item if Gitlab.ee?),
{
nav_item: _('Operations'),
nav_sub_items: [
@@ -101,8 +111,7 @@ RSpec.shared_context 'project navbar structure' do
_('Access Tokens'),
_('Repository'),
_('CI / CD'),
- _('Operations'),
- (_('Audit Events') if Gitlab.ee?)
+ _('Operations')
].compact
}
].compact
@@ -128,8 +137,7 @@ RSpec.shared_context 'group navbar structure' do
_('Projects'),
_('Repository'),
_('CI / CD'),
- _('Webhooks'),
- _('Audit Events')
+ _('Webhooks')
]
}
end
@@ -143,6 +151,15 @@ RSpec.shared_context 'group navbar structure' do
}
end
+ let(:security_and_compliance_nav_item) do
+ {
+ nav_item: _('Security & Compliance'),
+ nav_sub_items: [
+ _('Audit Events')
+ ]
+ }
+ end
+
let(:push_rules_nav_item) do
{
nav_item: _('Push Rules'),
@@ -172,6 +189,7 @@ RSpec.shared_context 'group navbar structure' do
nav_item: _('Merge Requests'),
nav_sub_items: []
},
+ (security_and_compliance_nav_item if Gitlab.ee?),
(push_rules_nav_item if Gitlab.ee?),
{
nav_item: _('Kubernetes'),
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index af46e5474b0..e0e2a18cdd2 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -30,6 +30,7 @@ RSpec.shared_context 'GroupPolicy context' do
let(:owner_permissions) do
[
+ :owner_access,
:admin_group,
:admin_namespace,
:admin_group_member,
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index 2bd516a2339..320f7564cf9 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -16,6 +16,8 @@ Service.available_services_names.each do |service|
hash.merge!(k => 'secrettoken')
elsif service == 'confluence' && k == :confluence_url
hash.merge!(k => 'https://example.atlassian.net/wiki')
+ elsif service == 'datadog' && k == :datadog_site
+ hash.merge!(k => 'datadoghq.com')
elsif k =~ /^(.*_url|url|webhook)/
hash.merge!(k => "http://example.com")
elsif service_klass.method_defined?("#{k}?")
@@ -34,8 +36,7 @@ Service.available_services_names.each do |service|
let(:licensed_features) do
{
- 'github' => :github_project_service_integration,
- 'jenkins' => :jenkins_integration
+ 'github' => :github_project_service_integration
}
end
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
index 38a5ed244c4..f89d52f81ad 100644
--- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -85,7 +85,7 @@ RSpec.shared_examples 'multiple issue boards' do
wait_for_requests
- expect(page).to have_selector('.board', count: 5)
+ expect(page).to have_selector('.board', count: 3)
in_boards_switcher_dropdown do
click_link board.name
@@ -93,7 +93,7 @@ RSpec.shared_examples 'multiple issue boards' do
wait_for_requests
- expect(page).to have_selector('.board', count: 4)
+ expect(page).to have_selector('.board', count: 2)
end
it 'maintains sidebar state over board switch' do
diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
index 5a4322f73b6..422282da4d8 100644
--- a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
@@ -219,7 +219,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'returns 200 response when the project is imported successfully' do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, format: :json
@@ -233,7 +233,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
project.errors.add(:path, 'is old')
allow(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, format: :json
@@ -244,7 +244,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it "touches the etag cache store" do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
expect_next_instance_of(Gitlab::EtagCaching::Store) do |store|
expect(store).to receive(:touch) { "realtime_changes_import_#{provider}_path" }
@@ -257,7 +257,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
context "when the provider user and GitLab user's usernames match" do
it "takes the current user's namespace" do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, format: :json
@@ -269,7 +269,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it "takes the current user's namespace" do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, format: :json
@@ -296,7 +296,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it "takes the existing namespace" do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, existing_namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, existing_namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, format: :json
@@ -308,7 +308,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
create(:user, username: other_username)
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, format: :json
@@ -327,7 +327,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it "takes the new namespace" do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, params: { target_namespace: provider_repo.name }, format: :json
@@ -348,7 +348,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it "takes the current user's namespace" do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, format: :json
@@ -366,7 +366,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'takes the selected namespace and name' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, test_namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, params: { target_namespace: test_namespace.name, new_name: test_name }, format: :json
@@ -374,7 +374,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'takes the selected name and default namespace' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, params: { new_name: test_name }, format: :json
@@ -393,7 +393,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'takes the selected namespace and name' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, nested_namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, params: { target_namespace: nested_namespace.full_path, new_name: test_name }, format: :json
@@ -405,7 +405,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'takes the selected namespace and name' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json
@@ -413,7 +413,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'creates the namespaces' do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, type: provider, **access_params)
.and_return(double(execute: project))
expect { post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json }
@@ -422,7 +422,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'new namespace has the right parent' do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json
@@ -441,7 +441,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'takes the selected namespace and name' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :json
@@ -449,7 +449,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'creates the namespaces' do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, type: provider, **access_params)
.and_return(double(execute: project))
expect { post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :json }
@@ -458,7 +458,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'does not create a new namespace under the user namespace' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
expect { post :create, params: { target_namespace: "#{user.namespace_path}/test_group", new_name: test_name }, format: :js }
@@ -472,7 +472,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'does not take the selected namespace and name' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, user.namespace, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :js
@@ -480,7 +480,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
it 'does not create the namespaces' do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, type: provider, **access_params)
.and_return(double(execute: project))
expect { post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :js }
@@ -497,7 +497,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
user.update!(can_create_group: false)
expect(Gitlab::LegacyGithubImport::ProjectCreator)
- .to receive(:new).with(provider_repo, test_name, group, user, access_params, type: provider)
+ .to receive(:new).with(provider_repo, test_name, group, user, type: provider, **access_params)
.and_return(double(execute: project))
post :create, params: { target_namespace: 'foo', new_name: test_name }, format: :js
diff --git a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
new file mode 100644
index 00000000000..9b738a4b002
--- /dev/null
+++ b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Repositories::GitHttpController do
+ include GitHttpHelpers
+
+ let(:repository_path) { "#{container.full_path}.git" }
+ let(:params) { { repository_path: repository_path } }
+
+ describe 'HEAD #info_refs' do
+ it 'returns 403' do
+ head :info_refs, params: params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ describe 'GET #info_refs' do
+ let(:params) { super().merge(service: 'git-upload-pack') }
+
+ it 'returns 401 for unauthenticated requests to public repositories when http protocol is disabled' do
+ stub_application_setting(enabled_git_access_protocol: 'ssh')
+ allow(controller).to receive(:basic_auth_provided?).and_call_original
+
+ expect(controller).to receive(:http_download_allowed?).and_call_original
+
+ get :info_refs, params: params
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'calls the right access checker class with the right object' do
+ allow(controller).to receive(:verify_workhorse_api!).and_return(true)
+
+ access_double = double
+ options = {
+ authentication_abilities: [:download_code],
+ repository_path: repository_path,
+ redirected_path: nil,
+ auth_result_type: :none
+ }
+
+ expect(access_checker_class).to receive(:new)
+ .with(nil, container, 'http', hash_including(options))
+ .and_return(access_double)
+
+ allow(access_double).to receive(:check).and_return(false)
+
+ get :info_refs, params: params
+ end
+
+ context 'with authorized user' do
+ before do
+ request.headers.merge! auth_env(user.username, user.password, nil)
+ end
+
+ it 'returns 200' do
+ get :info_refs, params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'updates the user activity' do
+ expect_next_instance_of(Users::ActivityService) do |activity_service|
+ expect(activity_service).to receive(:execute)
+ end
+
+ get :info_refs, params: params
+ end
+
+ include_context 'parsed logs' do
+ it 'adds user info to the logs' do
+ get :info_refs, params: params
+
+ expect(log_data).to include('username' => user.username,
+ 'user_id' => user.id,
+ 'meta.user' => user.username)
+ end
+ end
+ end
+
+ context 'with exceptions' do
+ before do
+ allow(controller).to receive(:authenticate_user).and_return(true)
+ allow(controller).to receive(:verify_workhorse_api!).and_return(true)
+ end
+
+ it 'returns 503 with GRPC Unavailable' do
+ allow(controller).to receive(:access_check).and_raise(GRPC::Unavailable)
+
+ get :info_refs, params: params
+
+ expect(response).to have_gitlab_http_status(:service_unavailable)
+ end
+
+ it 'returns 503 with timeout error' do
+ allow(controller).to receive(:access_check).and_raise(Gitlab::GitAccess::TimeoutError)
+
+ get :info_refs, params: params
+
+ expect(response).to have_gitlab_http_status(:service_unavailable)
+ expect(response.body).to eq 'Gitlab::GitAccess::TimeoutError'
+ end
+ end
+ end
+
+ describe 'POST #git_upload_pack' do
+ before do
+ allow(controller).to receive(:verify_workhorse_api!).and_return(true)
+ end
+
+ it 'returns 200' do
+ post :git_upload_pack, params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
index a6ad8fc594c..dcbf494186a 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -14,6 +14,22 @@ RSpec.shared_examples 'wiki controller actions' do
sign_in(user)
end
+ shared_examples 'recovers from git timeout' do
+ let(:method_name) { :page }
+
+ context 'when we encounter git command errors' do
+ it 'renders the appropriate template', :aggregate_failures do
+ expect(controller).to receive(method_name) do
+ raise ::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded'
+ end
+
+ request
+
+ expect(response).to render_template('shared/wikis/git_error')
+ end
+ end
+ end
+
describe 'GET #new' do
subject(:request) { get :new, params: routing_params }
@@ -48,6 +64,12 @@ RSpec.shared_examples 'wiki controller actions' do
get :pages, params: routing_params.merge(id: wiki_title)
end
+ it_behaves_like 'recovers from git timeout' do
+ subject(:request) { get :pages, params: routing_params.merge(id: wiki_title) }
+
+ let(:method_name) { :wiki_pages }
+ end
+
it 'assigns the page collections' do
expect(assigns(:wiki_pages)).to contain_exactly(an_instance_of(WikiPage))
expect(assigns(:wiki_entries)).to contain_exactly(an_instance_of(WikiPage))
@@ -99,6 +121,12 @@ RSpec.shared_examples 'wiki controller actions' do
end
end
+ it_behaves_like 'recovers from git timeout' do
+ subject(:request) { get :history, params: routing_params.merge(id: wiki_title) }
+
+ let(:allow_read_wiki) { true }
+ end
+
it_behaves_like 'fetching history', :ok do
let(:allow_read_wiki) { true }
@@ -139,6 +167,10 @@ RSpec.shared_examples 'wiki controller actions' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+ it_behaves_like 'recovers from git timeout' do
+ subject(:request) { get :diff, params: routing_params.merge(id: wiki_title, version_id: wiki.repository.commit.id) }
+ end
end
describe 'GET #show' do
@@ -151,6 +183,8 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when page exists' do
let(:id) { wiki_title }
+ it_behaves_like 'recovers from git timeout'
+
it 'renders the page' do
request
@@ -161,6 +195,28 @@ RSpec.shared_examples 'wiki controller actions' do
expect(assigns(:sidebar_limited)).to be(false)
end
+ context 'the sidebar fails to load' do
+ before do
+ allow(Wiki).to receive(:for_container).and_return(wiki)
+ wiki.wiki
+ expect(wiki).to receive(:find_sidebar) do
+ raise ::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded'
+ end
+ end
+
+ it 'renders the page, and marks the sidebar as failed' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/_sidebar')
+ expect(assigns(:page).title).to eq(wiki_title)
+ expect(assigns(:sidebar_page)).to be_nil
+ expect(assigns(:sidebar_wiki_entries)).to be_nil
+ expect(assigns(:sidebar_limited)).to be_nil
+ expect(assigns(:sidebar_error)).to be_a_kind_of(::Gitlab::Git::CommandError)
+ end
+ end
+
context 'page view tracking' do
it_behaves_like 'tracking unique hll events', :track_unique_wiki_page_views do
let(:target_id) { 'wiki_action' }
@@ -308,6 +364,7 @@ RSpec.shared_examples 'wiki controller actions' do
subject(:request) { get(:edit, params: routing_params.merge(id: id_param)) }
it_behaves_like 'edit action'
+ it_behaves_like 'recovers from git timeout'
context 'when page content encoding is valid' do
render_views
@@ -447,6 +504,17 @@ RSpec.shared_examples 'wiki controller actions' do
end
end
+ describe '#git_access' do
+ render_views
+
+ it 'renders the git access page' do
+ get :git_access, params: routing_params
+
+ expect(response).to render_template('shared/wikis/git_access')
+ expect(response.body).to include(wiki.http_url_to_repo)
+ end
+ end
+
def redirect_to_wiki(wiki, page)
redirect_to(controller.wiki_page_path(wiki, page))
end
diff --git a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
index ac1cc2da7e3..3fec1a56c0c 100644
--- a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
+++ b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
@@ -3,7 +3,7 @@
RSpec.shared_examples 'issuable invite members experiments' do
context 'when invite_members_version_a experiment is enabled' do
before do
- stub_experiment_for_user(invite_members_version_a: true)
+ stub_experiment_for_subject(invite_members_version_a: true)
end
it 'shows a link for inviting members and follows through to the members page' do
@@ -28,7 +28,7 @@ RSpec.shared_examples 'issuable invite members experiments' do
context 'when invite_members_version_b experiment is enabled' do
before do
- stub_experiment_for_user(invite_members_version_b: true)
+ stub_experiment_for_subject(invite_members_version_b: true)
end
it 'shows a link for inviting members and follows through to modal' do
diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
index 724d6db2705..1dbaace1c89 100644
--- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
+++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
@@ -50,7 +50,6 @@ RSpec.shared_examples 'Maintainer manages access requests' do
def expect_visible_access_request(entity, user)
if has_tabs
expect(page).to have_content "Access requests 1"
- expect(page).to have_content "Users requesting access to #{entity.name}"
else
expect(page).to have_content "Users requesting access to #{entity.name} 1"
end
diff --git a/spec/support/shared_examples/features/reportable_note_shared_examples.rb b/spec/support/shared_examples/features/reportable_note_shared_examples.rb
index bdaa375721f..288e1df9b2a 100644
--- a/spec/support/shared_examples/features/reportable_note_shared_examples.rb
+++ b/spec/support/shared_examples/features/reportable_note_shared_examples.rb
@@ -29,7 +29,7 @@ RSpec.shared_examples 'reportable note' do |type|
end
end
- it 'Report button links to a report page' do
+ it 'report button links to a report page' do
dropdown = comment.find(more_actions_selector)
open_dropdown(dropdown)
diff --git a/spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb
new file mode 100644
index 00000000000..d3d2a36147d
--- /dev/null
+++ b/spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'User views Git access wiki page' do
+ let(:wiki_page) { create(:wiki_page, wiki: wiki) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'shows the correct clone URLs', :js do
+ visit wiki_page_path(wiki, wiki_page)
+ click_link 'Clone repository'
+
+ expect(page).to have_text("Clone repository #{wiki.full_path}")
+
+ within('.git-clone-holder') do
+ expect(page).to have_css('#clone-dropdown', text: 'HTTP')
+ expect(page).to have_field('clone_url', with: wiki.http_url_to_repo)
+
+ click_link 'HTTP' # open the dropdown
+ click_link 'SSH' # select the dropdown item
+
+ expect(page).to have_css('#clone-dropdown', text: 'SSH')
+ expect(page).to have_field('clone_url', with: wiki.ssh_url_to_repo)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/wiki/user_uses_wiki_shortcuts_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_uses_wiki_shortcuts_shared_examples.rb
index 0330b345a18..759cfaf6b1f 100644
--- a/spec/support/shared_examples/features/wiki/user_uses_wiki_shortcuts_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_uses_wiki_shortcuts_shared_examples.rb
@@ -12,7 +12,7 @@ RSpec.shared_examples 'User uses wiki shortcuts' do
visit wiki_page_path(wiki, wiki_page)
end
- it 'Visit edit wiki page using "e" keyboard shortcut', :js do
+ it 'visit edit wiki page using "e" keyboard shortcut', :js do
find('body').native.send_key('e')
expect(find('.wiki-page-title')).to have_content('Edit Page')
diff --git a/spec/support/shared_examples/graphql/connection_redaction_shared_examples.rb b/spec/support/shared_examples/graphql/connection_redaction_shared_examples.rb
new file mode 100644
index 00000000000..12a7b3fe414
--- /dev/null
+++ b/spec/support/shared_examples/graphql/connection_redaction_shared_examples.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+# requires:
+# - `connection` (no-empty, containing `unwanted` and at least one more item)
+# - `unwanted` (single item in collection)
+RSpec.shared_examples 'a redactable connection' do
+ context 'no redactor set' do
+ it 'contains the unwanted item' do
+ expect(connection.nodes).to include(unwanted)
+ end
+
+ it 'does not redact more than once' do
+ connection.nodes
+ r_state = connection.send(:redaction_state)
+
+ expect(r_state.redacted { raise 'Should not be called!' }).to be_present
+ end
+ end
+
+ let_it_be(:constant_redactor) do
+ Class.new do
+ def initialize(remove)
+ @remove = remove
+ end
+
+ def redact(items)
+ items - @remove
+ end
+ end
+ end
+
+ context 'redactor is set' do
+ let(:redactor) do
+ constant_redactor.new([unwanted])
+ end
+
+ before do
+ connection.redactor = redactor
+ end
+
+ it 'does not contain the unwanted item' do
+ expect(connection.nodes).not_to include(unwanted)
+ expect(connection.nodes).not_to be_empty
+ end
+
+ it 'does not redact more than once' do
+ expect(redactor).to receive(:redact).once.and_call_original
+
+ connection.nodes
+ connection.nodes
+ connection.nodes
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/connection_shared_examples.rb b/spec/support/shared_examples/graphql/connection_shared_examples.rb
new file mode 100644
index 00000000000..4cba5b5a69d
--- /dev/null
+++ b/spec/support/shared_examples/graphql/connection_shared_examples.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a connection with collection methods' do
+ %i[to_a size include? empty?].each do |method_name|
+ it "responds to #{method_name}" do
+ expect(connection).to respond_to(method_name)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/design_fields_shared_examples.rb b/spec/support/shared_examples/graphql/design_fields_shared_examples.rb
index ef7086234c4..9c2eb3e5a5c 100644
--- a/spec/support/shared_examples/graphql/design_fields_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/design_fields_shared_examples.rb
@@ -26,27 +26,35 @@ RSpec.shared_examples 'a GraphQL type with design fields' do
end
describe '#image' do
+ let_it_be(:current_user) { create(:user) }
let(:schema) { GitlabSchema }
let(:query) { GraphQL::Query.new(schema) }
- let(:context) { double('Context', schema: schema, query: query, parent: nil) }
+ let(:context) { query.context }
let(:field) { described_class.fields['image'] }
let(:args) { GraphQL::Query::Arguments::NO_ARGS }
- let(:instance) do
+ let(:instance) { instantiate(object_id) }
+ let(:instance_b) { instantiate(object_id_b) }
+
+ def instantiate(object_id)
object = GitlabSchema.sync_lazy(GitlabSchema.object_from_id(object_id))
object_type.authorized_new(object, query.context)
end
- let(:instance_b) do
- object_b = GitlabSchema.sync_lazy(GitlabSchema.object_from_id(object_id_b))
- object_type.authorized_new(object_b, query.context)
+ def resolve_image(instance)
+ field.resolve_field(instance, args, context)
+ end
+
+ before do
+ context[:current_user] = current_user
+ allow(Ability).to receive(:allowed?).with(current_user, :read_design, anything).and_return(true)
+ allow(context).to receive(:parent).and_return(nil)
end
it 'resolves to the design image URL' do
- image = field.resolve_field(instance, args, context)
sha = design.versions.first.sha
url = ::Gitlab::Routing.url_helpers.project_design_management_designs_raw_image_url(design.project, design, sha)
- expect(image).to eq(url)
+ expect(resolve_image(instance)).to eq(url)
end
it 'has better than O(N) peformance', :request_store do
@@ -68,10 +76,10 @@ RSpec.shared_examples 'a GraphQL type with design fields' do
# = 10
expect(instance).not_to eq(instance_b) # preload designs themselves.
expect do
- image_a = field.resolve_field(instance, args, context)
- image_b = field.resolve_field(instance, args, context)
- image_c = field.resolve_field(instance_b, args, context)
- image_d = field.resolve_field(instance_b, args, context)
+ image_a = resolve_image(instance)
+ image_b = resolve_image(instance)
+ image_c = resolve_image(instance_b)
+ image_d = resolve_image(instance_b)
expect(image_a).to eq(image_b)
expect(image_c).not_to eq(image_b)
expect(image_c).to eq(image_d)
diff --git a/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
deleted file mode 100644
index b2047f1d32c..00000000000
--- a/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'no Jira import data present' do
- it 'returns none' do
- expect(resolve_imports).to eq JiraImportState.none
- end
-end
-
-RSpec.shared_examples 'no Jira import access' do
- it 'raises error' do
- expect do
- resolve_imports
- end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
- end
-end
diff --git a/spec/support/shared_examples/graphql/members_shared_examples.rb b/spec/support/shared_examples/graphql/members_shared_examples.rb
index 3a046c3feec..b0bdd27a95f 100644
--- a/spec/support/shared_examples/graphql/members_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/members_shared_examples.rb
@@ -36,9 +36,10 @@ RSpec.shared_examples 'querying members with a group' do
let_it_be(:group_2_member) { create(:group_member, user: user_3, group: group_2) }
let(:args) { {} }
+ let(:base_args) { { relations: described_class.arguments['relations'].default_value } }
subject do
- resolve(described_class, obj: resource, args: args, ctx: { current_user: user_4 })
+ resolve(described_class, obj: resource, args: base_args.merge(args), ctx: { current_user: user_4 })
end
describe '#resolve' do
@@ -72,7 +73,7 @@ RSpec.shared_examples 'querying members with a group' do
let_it_be(:other_user) { create(:user) }
subject do
- resolve(described_class, obj: resource, args: args, ctx: { current_user: other_user })
+ resolve(described_class, obj: resource, args: base_args.merge(args), ctx: { current_user: other_user })
end
it 'raises an error' do
diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
index b67cac94547..84ebd4852b9 100644
--- a/spec/support/shared_examples/graphql/mutation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
@@ -13,6 +13,8 @@ RSpec.shared_examples 'a mutation that returns top-level errors' do |errors: []|
it do
post_graphql_mutation(mutation, current_user: current_user)
+ expect(graphql_errors).to be_present
+
error_messages = graphql_errors.map { |e| e['message'] }
expect(error_messages).to match_errors
diff --git a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
index 9c0b398a5c1..2b93d174653 100644
--- a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
@@ -40,6 +40,30 @@ RSpec.shared_examples 'boards create mutation' do
end
end
+ context 'when hide_backlog_list parameter is true' do
+ before do
+ params[:hide_backlog_list] = true
+ end
+
+ it 'returns the board with correct hide_backlog_list field' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['board']['hideBacklogList']).to eq(true)
+ end
+ end
+
+ context 'when hide_closed_list parameter is true' do
+ before do
+ params[:hide_closed_list] = true
+ end
+
+ it 'returns the board with correct hide_closed_list field' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['board']['hideClosedList']).to eq(true)
+ end
+ end
+
context 'when the Boards::CreateService returns an error response' do
before do
allow_next_instance_of(Boards::CreateService) do |service|
diff --git a/spec/support/shared_examples/graphql/mutations/spammable_mutation_fields_examples.rb b/spec/support/shared_examples/graphql/mutations/spammable_mutation_fields_examples.rb
index 54b3f84a6e6..8678b23ad31 100644
--- a/spec/support/shared_examples/graphql/mutations/spammable_mutation_fields_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/spammable_mutation_fields_examples.rb
@@ -13,7 +13,8 @@ end
RSpec.shared_examples 'can raise spam flag' do
it 'spam parameters are passed to the service' do
- expect(service).to receive(:new).with(anything, anything, hash_including(api: true, request: instance_of(ActionDispatch::Request)))
+ args = [anything, anything, hash_including(api: true, request: instance_of(ActionDispatch::Request))]
+ expect(service).to receive(:new).with(*args).and_call_original
subject
end
@@ -39,7 +40,9 @@ RSpec.shared_examples 'can raise spam flag' do
end
it 'request parameter is not passed to the service' do
- expect(service).to receive(:new).with(anything, anything, hash_not_including(request: instance_of(ActionDispatch::Request)))
+ expect(service).to receive(:new)
+ .with(anything, anything, hash_not_including(request: instance_of(ActionDispatch::Request)))
+ .and_call_original
subject
end
diff --git a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
index 94b7ed1618d..16c2ab07f3a 100644
--- a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
@@ -2,14 +2,12 @@
RSpec.shared_examples 'no project services' do
it 'returns empty collection' do
- expect(resolve_services).to eq []
+ expect(resolve_services).to be_empty
end
end
RSpec.shared_examples 'cannot access project services' do
it 'raises error' do
- expect do
- resolve_services
- end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ expect(resolve_services).to be_nil
end
end
diff --git a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
index 7627a7b4d59..f78ea364147 100644
--- a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
@@ -16,80 +16,111 @@
#
# Example:
# describe 'sorting and pagination' do
-# let(:sort_project) { create(:project, :public) }
+# let_it_be(:sort_project) { create(:project, :public) }
# let(:data_path) { [:project, :issues] }
#
-# def pagination_query(params, page_info)
-# graphql_query_for(
-# 'project',
-# { 'fullPath' => sort_project.full_path },
-# query_graphql_field('issues', params, "#{page_info} edges { node { id } }")
+# def pagination_query(arguments)
+# graphql_query_for(:project, { full_path: sort_project.full_path },
+# query_nodes(:issues, :iid, include_pagination_info: true, args: arguments)
# )
# end
#
-# def pagination_results_data(data)
-# data.map { |issue| issue.dig('node', 'iid').to_i }
+# # A method transforming nodes to data to match against
+# # default: the identity function
+# def pagination_results_data(issues)
+# issues.map { |issue| issue['iid].to_i }
# end
#
# context 'when sorting by weight' do
-# ...
+# let_it_be(:issues) { make_some_issues_with_weights }
+#
# context 'when ascending' do
+# let(:ordered_issues) { issues.sort_by(&:weight) }
+#
# it_behaves_like 'sorted paginated query' do
-# let(:sort_param) { 'WEIGHT_ASC' }
+# let(:sort_param) { :WEIGHT_ASC }
# let(:first_param) { 2 }
-# let(:expected_results) { [weight_issue3.iid, weight_issue5.iid, weight_issue1.iid, weight_issue4.iid, weight_issue2.iid] }
+# let(:expected_results) { ordered_issues.map(&:iid) }
# end
# end
#
RSpec.shared_examples 'sorted paginated query' do
+ # Provided as a convenience when constructing queries using string concatenation
+ let(:page_info) { 'pageInfo { startCursor endCursor }' }
+ # Convenience for using default implementation of pagination_results_data
+ let(:node_path) { ['id'] }
+
it_behaves_like 'requires variables' do
let(:required_variables) { [:sort_param, :first_param, :expected_results, :data_path, :current_user] }
end
describe do
- let(:sort_argument) { "sort: #{sort_param}" if sort_param.present? }
- let(:first_argument) { "first: #{first_param}" if first_param.present? }
+ let(:sort_argument) { graphql_args(sort: sort_param) }
let(:params) { sort_argument }
- let(:start_cursor) { graphql_data_at(*data_path, :pageInfo, :startCursor) }
- let(:end_cursor) { graphql_data_at(*data_path, :pageInfo, :endCursor) }
- let(:sorted_edges) { graphql_data_at(*data_path, :edges) }
- let(:page_info) { "pageInfo { startCursor endCursor }" }
- def pagination_query(params, page_info)
- raise('pagination_query(params, page_info) must be defined in the test, see example in comment') unless defined?(super)
+ # Convenience helper for the large number of queries defined as a projection
+ # from some root value indexed by full_path to a collection of objects with IID
+ def nested_internal_id_query(root_field, parent, field, args, selection: :iid)
+ graphql_query_for(root_field, { full_path: parent.full_path },
+ query_nodes(field, selection, args: args, include_pagination_info: true)
+ )
+ end
+
+ def pagination_query(params)
+ raise('pagination_query(params) must be defined in the test, see example in comment') unless defined?(super)
super
end
- def pagination_results_data(data)
- raise('pagination_results_data(data) must be defined in the test, see example in comment') unless defined?(super)
+ def pagination_results_data(nodes)
+ if defined?(super)
+ super(nodes)
+ else
+ nodes.map { |n| n.dig(*node_path) }
+ end
+ end
+
+ def results
+ nodes = graphql_dig_at(graphql_data(fresh_response_data), *data_path, :nodes)
+ pagination_results_data(nodes)
+ end
+
+ def end_cursor
+ graphql_dig_at(graphql_data(fresh_response_data), *data_path, :page_info, :end_cursor)
+ end
- super(data)
+ def start_cursor
+ graphql_dig_at(graphql_data(fresh_response_data), *data_path, :page_info, :start_cursor)
end
+ let(:query) { pagination_query(params) }
+
before do
- post_graphql(pagination_query(params, page_info), current_user: current_user)
+ post_graphql(query, current_user: current_user)
end
context 'when sorting' do
it 'sorts correctly' do
- expect(pagination_results_data(sorted_edges)).to eq expected_results
+ expect(results).to eq expected_results
end
context 'when paginating' do
- let(:params) { [sort_argument, first_argument].compact.join(',') }
+ let(:params) { sort_argument.merge(first: first_param) }
+ let(:first_page) { expected_results.first(first_param) }
+ let(:rest) { expected_results.drop(first_param) }
it 'paginates correctly' do
- expect(pagination_results_data(sorted_edges)).to eq expected_results.first(first_param)
+ expect(results).to eq first_page
- cursored_query = pagination_query([sort_argument, "after: \"#{end_cursor}\""].compact.join(','), page_info)
- post_graphql(cursored_query, current_user: current_user)
+ fwds = pagination_query(sort_argument.merge(after: end_cursor))
+ post_graphql(fwds, current_user: current_user)
- expect(response).to have_gitlab_http_status(:ok)
+ expect(results).to eq rest
- response_data = graphql_dig_at(Gitlab::Json.parse(response.body), :data, *data_path, :edges)
+ bwds = pagination_query(sort_argument.merge(before: start_cursor))
+ post_graphql(bwds, current_user: current_user)
- expect(pagination_results_data(response_data)).to eq expected_results.drop(first_param)
+ expect(results).to eq first_page
end
end
end
diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
index ed139e638bf..269e9170906 100644
--- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
@@ -32,16 +32,16 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
it 'adds a formatted `deprecated_reason` to the subject' do
deprecable = subject(deprecated: { milestone: '1.10', reason: 'Deprecation reason' })
- expect(deprecable.deprecation_reason).to eq('Deprecation reason. Deprecated in 1.10')
+ expect(deprecable.deprecation_reason).to eq('Deprecation reason. Deprecated in 1.10.')
end
it 'appends to the description if given' do
deprecable = subject(
deprecated: { milestone: '1.10', reason: 'Deprecation reason' },
- description: 'Deprecable description'
+ description: 'Deprecable description.'
)
- expect(deprecable.description).to eq('Deprecable description. Deprecated in 1.10: Deprecation reason')
+ expect(deprecable.description).to eq('Deprecable description. Deprecated in 1.10: Deprecation reason.')
end
it 'does not append to the description if it is absent' do
diff --git a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
index 469c0c287b1..c9e03ced0dd 100644
--- a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
@@ -143,3 +143,55 @@ RSpec.shared_examples 'cacheable diff collection' do
end
end
end
+
+shared_examples_for 'sortable diff files' do
+ subject { described_class.new(diffable, **collection_default_args) }
+
+ describe '#raw_diff_files' do
+ let(:raw_diff_files_paths) do
+ subject.raw_diff_files(sorted: sorted).map { |file| file.new_path.presence || file.old_path }
+ end
+
+ context 'when sorted is false (default)' do
+ let(:sorted) { false }
+
+ it 'returns unsorted diff files' do
+ expect(raw_diff_files_paths).to eq(unsorted_diff_files_paths)
+ end
+ end
+
+ context 'when sorted is true' do
+ let(:sorted) { true }
+
+ it 'returns sorted diff files' do
+ expect(raw_diff_files_paths).to eq(sorted_diff_files_paths)
+ end
+
+ context 'when sort_diffs feature flag is disabled' do
+ before do
+ stub_feature_flags(sort_diffs: false)
+ end
+
+ it 'returns unsorted diff files' do
+ expect(raw_diff_files_paths).to eq(unsorted_diff_files_paths)
+ end
+ end
+ end
+ end
+end
+
+shared_examples_for 'unsortable diff files' do
+ subject { described_class.new(diffable, **collection_default_args) }
+
+ describe '#raw_diff_files' do
+ it 'does not call Gitlab::Diff::FileCollectionSorter even when sorted is true' do
+ # Ensure that diffable is created before expectation to ensure that we are
+ # not calling it from `FileCollectionSorter` from `#raw_diff_files`.
+ diffable
+
+ expect(Gitlab::Diff::FileCollectionSorter).not_to receive(:new)
+
+ subject.raw_diff_files(sorted: true)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/import_export/import_failure_service_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/import_failure_service_shared_examples.rb
index 801be5ae946..67afd2035c4 100644
--- a/spec/support/shared_examples/lib/gitlab/import_export/import_failure_service_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import_export/import_failure_service_shared_examples.rb
@@ -3,10 +3,10 @@
RSpec.shared_examples 'log import failure' do |importable_column|
it 'tracks error' do
extra = {
- source: action,
- relation_key: relation_key,
- relation_index: relation_index,
- retry_count: retry_count
+ source: action,
+ relation_name: relation_key,
+ relation_index: relation_index,
+ retry_count: retry_count
}
extra[importable_column] = importable.id
diff --git a/spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb
index e07d3e2dec9..5b3d30df739 100644
--- a/spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb
@@ -125,6 +125,9 @@ RSpec.shared_examples 'write access for a read-only GitLab instance' do
where(:description, :path) do
'LFS request to batch' | '/root/rouge.git/info/lfs/objects/batch'
'request to git-upload-pack' | '/root/rouge.git/git-upload-pack'
+ 'user sign out' | '/users/sign_out'
+ 'admin session' | '/admin/session'
+ 'admin session destroy' | '/admin/session/destroy'
end
with_them do
diff --git a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
index 2936bb354cf..89b793d5e16 100644
--- a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
@@ -38,7 +38,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
it 'adds the jid of the existing job to the job hash' do
allow(fake_duplicate_job).to receive(:scheduled?).and_return(false)
allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
- allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
allow(fake_duplicate_job).to receive(:options).and_return({})
job_hash = {}
@@ -62,7 +62,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
receive(:check!)
.with(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob::DUPLICATE_KEY_TTL)
.and_return('the jid'))
- allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
job_hash = {}
expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
@@ -82,7 +82,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
allow(fake_duplicate_job).to receive(:options).and_return({ including_scheduled: true })
allow(fake_duplicate_job).to(
receive(:check!).with(time_diff.to_i).and_return('the jid'))
- allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
job_hash = {}
expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
@@ -104,13 +104,13 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
allow(fake_duplicate_job).to receive(:duplicate?).and_return(true)
allow(fake_duplicate_job).to receive(:options).and_return({})
allow(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
- allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
end
it 'drops the job' do
schedule_result = nil
- expect(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ expect(fake_duplicate_job).to receive(:idempotent?).and_return(true)
expect { |b| schedule_result = strategy.schedule({}, &b) }.not_to yield_control
expect(schedule_result).to be(false)
diff --git a/spec/support/shared_examples/models/concerns/can_move_repository_storage_shared_examples.rb b/spec/support/shared_examples/models/concerns/can_move_repository_storage_shared_examples.rb
new file mode 100644
index 00000000000..85a2c6f1449
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/can_move_repository_storage_shared_examples.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'can move repository storage' do
+ let(:container) { raise NotImplementedError }
+
+ describe '#set_repository_read_only!' do
+ it 'makes the repository read-only' do
+ expect { container.set_repository_read_only! }
+ .to change(container, :repository_read_only?)
+ .from(false)
+ .to(true)
+ end
+
+ it 'raises an error if the project is already read-only' do
+ container.set_repository_read_only!
+
+ expect { container.set_repository_read_only! }.to raise_error(described_class::RepositoryReadOnlyError, /already read-only/)
+ end
+
+ it 'raises an error when there is an existing git transfer in progress' do
+ allow(container).to receive(:git_transfer_in_progress?) { true }
+
+ expect { container.set_repository_read_only! }.to raise_error(described_class::RepositoryReadOnlyError, /in progress/)
+ end
+
+ context 'skip_git_transfer_check is true' do
+ it 'makes the project read-only when git transfers are in progress' do
+ allow(container).to receive(:git_transfer_in_progress?) { true }
+
+ expect { container.set_repository_read_only!(skip_git_transfer_check: true) }
+ .to change(container, :repository_read_only?)
+ .from(false)
+ .to(true)
+ end
+ end
+ end
+
+ describe '#set_repository_writable!' do
+ it 'sets repository_read_only to false' do
+ expect { container.set_repository_writable! }
+ .to change(container, :repository_read_only)
+ .from(true).to(false)
+ end
+ end
+
+ describe '#reference_counter' do
+ it 'returns a Gitlab::ReferenceCounter object' do
+ expect(Gitlab::ReferenceCounter).to receive(:new).with(container.repository.gl_repository).and_call_original
+
+ result = container.reference_counter(type: container.repository.repo_type)
+
+ expect(result).to be_a Gitlab::ReferenceCounter
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb b/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
new file mode 100644
index 00000000000..5a8388d01df
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'handles repository moves' do
+ describe 'associations' do
+ it { is_expected.to belong_to(:container) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:container) }
+ it { is_expected.to validate_presence_of(:state) }
+ it { is_expected.to validate_presence_of(:source_storage_name) }
+ it { is_expected.to validate_presence_of(:destination_storage_name) }
+
+ context 'source_storage_name inclusion' do
+ subject { build(repository_storage_factory_key, source_storage_name: 'missing') }
+
+ it "does not allow repository storages that don't match a label in the configuration" do
+ expect(subject).not_to be_valid
+ expect(subject.errors[:source_storage_name].first).to match(/is not included in the list/)
+ end
+ end
+
+ context 'destination_storage_name inclusion' do
+ subject { build(repository_storage_factory_key, destination_storage_name: 'missing') }
+
+ it "does not allow repository storages that don't match a label in the configuration" do
+ expect(subject).not_to be_valid
+ expect(subject.errors[:destination_storage_name].first).to match(/is not included in the list/)
+ end
+ end
+
+ context 'container repository read-only' do
+ subject { build(repository_storage_factory_key, container: container) }
+
+ it "does not allow the container to be read-only on create" do
+ container.update!(repository_read_only: true)
+
+ expect(subject).not_to be_valid
+ expect(subject.errors[error_key].first).to match(/is read only/)
+ end
+ end
+ end
+
+ describe 'defaults' do
+ context 'destination_storage_name' do
+ subject { build(repository_storage_factory_key) }
+
+ it 'picks storage from ApplicationSetting' do
+ expect(Gitlab::CurrentSettings).to receive(:pick_repository_storage).and_return('picked').at_least(:once)
+
+ expect(subject.destination_storage_name).to eq('picked')
+ end
+ end
+ end
+
+ describe 'state transitions' do
+ before do
+ stub_storage_settings('test_second_storage' => { 'path' => 'tmp/tests/extra_storage' })
+ end
+
+ context 'when in the default state' do
+ subject(:storage_move) { create(repository_storage_factory_key, container: container, destination_storage_name: 'test_second_storage') }
+
+ context 'and transits to scheduled' do
+ it 'triggers the corresponding repository storage worker' do
+ skip unless repository_storage_worker # TODO remove after https://gitlab.com/gitlab-org/gitlab/-/issues/218991 is implemented
+ expect(repository_storage_worker).to receive(:perform_async).with(container.id, 'test_second_storage', storage_move.id)
+
+ storage_move.schedule!
+
+ expect(container).to be_repository_read_only
+ end
+
+ context 'when the transition fails' do
+ it 'does not trigger ProjectUpdateRepositoryStorageWorker and adds an error' do
+ skip unless repository_storage_worker # TODO remove after https://gitlab.com/gitlab-org/gitlab/-/issues/218991 is implemented
+ allow(storage_move.container).to receive(:set_repository_read_only!).and_raise(StandardError, 'foobar')
+ expect(repository_storage_worker).not_to receive(:perform_async)
+
+ storage_move.schedule!
+
+ expect(storage_move.errors[error_key]).to include('foobar')
+ end
+ end
+ end
+
+ context 'and transits to started' do
+ it 'does not allow the transition' do
+ expect { storage_move.start! }
+ .to raise_error(StateMachines::InvalidTransition)
+ end
+ end
+ end
+
+ context 'when started' do
+ subject(:storage_move) { create(repository_storage_factory_key, :started, container: container, destination_storage_name: 'test_second_storage') }
+
+ context 'and transits to replicated' do
+ it 'marks the container as writable' do
+ storage_move.finish_replication!
+
+ expect(container).not_to be_repository_read_only
+ end
+ end
+
+ context 'and transits to failed' do
+ it 'marks the container as writable' do
+ storage_move.do_fail!
+
+ expect(container).not_to be_repository_read_only
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/shardable_shared_examples.rb b/spec/support/shared_examples/models/concerns/shardable_shared_examples.rb
index fa929d5b791..fd0639b628e 100644
--- a/spec/support/shared_examples/models/concerns/shardable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/shardable_shared_examples.rb
@@ -18,4 +18,10 @@ RSpec.shared_examples 'shardable scopes' do
expect(described_class.excluding_repository_storage('default')).to eq([record_2])
end
end
+
+ describe '.for_shard' do
+ it 'returns the objects for a given shard' do
+ expect(described_class.for_shard(record_1.shard)).to eq([record_1])
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/mentionable_shared_examples.rb b/spec/support/shared_examples/models/mentionable_shared_examples.rb
index 0ee0b7e6d88..2392658e584 100644
--- a/spec/support/shared_examples/models/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/models/mentionable_shared_examples.rb
@@ -92,7 +92,7 @@ RSpec.shared_examples 'a mentionable' do
end
end
- expect(subject).to receive(:cached_markdown_fields).at_least(:once).and_call_original
+ expect(subject).to receive(:cached_markdown_fields).at_least(1).and_call_original
subject.all_references(author)
end
@@ -151,7 +151,7 @@ RSpec.shared_examples 'an editable mentionable' do
end
it 'persists the refreshed cache so that it does not have to be refreshed every time' do
- expect(subject).to receive(:refresh_markdown_cache).once.and_call_original
+ expect(subject).to receive(:refresh_markdown_cache).at_least(1).and_call_original
subject.all_references(author)
diff --git a/spec/support/shared_examples/models/resource_timebox_event_shared_examples.rb b/spec/support/shared_examples/models/resource_timebox_event_shared_examples.rb
index 5198508d48b..f56e8d4e085 100644
--- a/spec/support/shared_examples/models/resource_timebox_event_shared_examples.rb
+++ b/spec/support/shared_examples/models/resource_timebox_event_shared_examples.rb
@@ -75,11 +75,17 @@ RSpec.shared_examples 'timebox resource event actions' do
end
RSpec.shared_examples 'timebox resource tracks issue metrics' do |type|
- describe '#usage_metrics' do
- it 'tracks usage' do
+ describe '#issue_usage_metrics' do
+ it 'tracks usage for issues' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:"track_issue_#{type}_changed_action")
create(described_class.name.underscore.to_sym, issue: create(:issue))
end
+
+ it 'does not track usage for merge requests' do
+ expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:"track_issue_#{type}_changed_action")
+
+ create(described_class.name.underscore.to_sym, merge_request: create(:merge_request))
+ end
end
end
diff --git a/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..a99304f7214
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb
@@ -0,0 +1,187 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'clone quick action' do
+ context 'clone the issue to another project' do
+ let(:target_project) { create(:project, :public) }
+
+ context 'when no target is given' do
+ it 'clones the issue in the current project' do
+ add_note("/clone")
+
+ expect(page).to have_content "Cloned this issue to #{project.full_path}."
+ expect(issue.reload).to be_open
+
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_content 'Issues 2'
+ end
+ end
+
+ context 'when the project is valid' do
+ before do
+ target_project.add_maintainer(user)
+ end
+
+ it 'clones the issue' do
+ add_note("/clone #{target_project.full_path}")
+
+ expect(page).to have_content "Cloned this issue to #{target_project.full_path}."
+ expect(issue.reload).to be_open
+
+ visit project_issue_path(target_project, issue)
+
+ expect(page).to have_content 'Issues 1'
+ end
+
+ context 'when cloning with notes', :aggregate_failures do
+ it 'clones the issue with all notes' do
+ add_note("Some random note")
+ add_note("Another note")
+
+ add_note("/clone --with_notes #{target_project.full_path}")
+
+ expect(page).to have_content "Cloned this issue to #{target_project.full_path}."
+ expect(issue.reload).to be_open
+
+ visit project_issue_path(target_project, issue)
+
+ expect(page).to have_content 'Issues 1'
+ expect(page).to have_content 'Some random note'
+ expect(page).to have_content 'Another note'
+ end
+
+ it 'returns an error if the params are malformed' do
+ # Note that this is missing one `-`
+ add_note("/clone -with_notes #{target_project.full_path}")
+
+ wait_for_requests
+
+ expect(page).to have_content 'Failed to clone this issue: wrong parameters.'
+ expect(issue.reload).to be_open
+ end
+ end
+ end
+
+ context 'when the project is valid but the user not authorized' do
+ let(:project_unauthorized) { create(:project, :public) }
+
+ it 'does not clone the issue' do
+ add_note("/clone #{project_unauthorized.full_path}")
+
+ wait_for_requests
+
+ expect(page).to have_content "Cloned this issue to #{project_unauthorized.full_path}."
+ expect(issue.reload).to be_open
+
+ visit project_issue_path(target_project, issue)
+
+ expect(page).not_to have_content 'Issues 1'
+ end
+ end
+
+ context 'when the project is invalid' do
+ it 'does not clone the issue' do
+ add_note("/clone not/valid")
+
+ wait_for_requests
+
+ expect(page).to have_content "Failed to clone this issue because target project doesn't exist."
+ expect(issue.reload).to be_open
+ end
+ end
+
+ context 'when the user issues multiple commands' do
+ let(:milestone) { create(:milestone, title: '1.0', project: project) }
+ let(:bug) { create(:label, project: project, title: 'bug') }
+ let(:wontfix) { create(:label, project: project, title: 'wontfix') }
+
+ let!(:target_milestone) { create(:milestone, title: '1.0', project: target_project) }
+
+ before do
+ target_project.add_maintainer(user)
+ end
+
+ shared_examples 'applies the commands to issues in both projects, target and source' do
+ it "applies quick actions" do
+ expect(page).to have_content "Cloned this issue to #{target_project.full_path}."
+ expect(issue.reload).to be_open
+
+ visit project_issue_path(target_project, issue)
+
+ expect(page).to have_content 'bug'
+ expect(page).to have_content 'wontfix'
+ expect(page).to have_content '1.0'
+
+ visit project_issue_path(project, issue)
+ expect(page).to have_content 'bug'
+ expect(page).to have_content 'wontfix'
+ expect(page).to have_content '1.0'
+ end
+ end
+
+ context 'applies multiple commands with clone command in the end' do
+ before do
+ add_note("/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"\n\n/clone #{target_project.full_path}")
+ end
+
+ it_behaves_like 'applies the commands to issues in both projects, target and source'
+ end
+
+ context 'applies multiple commands with clone command in the begining' do
+ before do
+ add_note("/clone #{target_project.full_path}\n\n/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"")
+ end
+
+ it_behaves_like 'applies the commands to issues in both projects, target and source'
+ end
+ end
+
+ context 'when editing comments' do
+ let(:target_project) { create(:project, :public) }
+
+ before do
+ target_project.add_maintainer(user)
+
+ sign_in(user)
+ visit project_issue_path(project, issue)
+ wait_for_all_requests
+ end
+
+ it 'clones the issue after quickcommand note was updated' do
+ # misspelled quick action
+ add_note("test note.\n/cloe #{target_project.full_path}")
+
+ expect(issue.reload).not_to be_closed
+
+ edit_note("/cloe #{target_project.full_path}", "test note.\n/clone #{target_project.full_path}")
+ wait_for_all_requests
+
+ expect(page).to have_content 'test note.'
+ expect(issue.reload).to be_open
+
+ visit project_issue_path(target_project, issue)
+ wait_for_all_requests
+
+ expect(page).to have_content 'Issues 1'
+ end
+
+ it 'deletes the note if it was updated to just contain a command' do
+ # missspelled quick action
+ add_note("test note.\n/cloe #{target_project.full_path}")
+
+ expect(page).not_to have_content 'Commands applied'
+
+ edit_note("/cloe #{target_project.full_path}", "/clone #{target_project.full_path}")
+ wait_for_all_requests
+
+ expect(page).not_to have_content "/clone #{target_project.full_path}"
+ expect(issue.reload).to be_open
+
+ visit project_issue_path(target_project, issue)
+ wait_for_all_requests
+
+ expect(page).to have_content 'Issues 1'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
index c56290a0aa9..49b6fc13900 100644
--- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
@@ -629,6 +629,7 @@ RSpec.shared_examples 'workhorse recipe file upload endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'uploads a package file'
+ it_behaves_like 'creates build_info when there is a job'
end
RSpec.shared_examples 'workhorse package file upload endpoint' do
@@ -649,6 +650,7 @@ RSpec.shared_examples 'workhorse package file upload endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conaninfo.txttest'
it_behaves_like 'uploads a package file'
+ it_behaves_like 'creates build_info when there is a job'
context 'tracking the conan_package.tgz upload' do
let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
@@ -657,6 +659,20 @@ RSpec.shared_examples 'workhorse package file upload endpoint' do
end
end
+RSpec.shared_examples 'creates build_info when there is a job' do
+ context 'with job token' do
+ let(:jwt) { build_jwt_from_job(job) }
+
+ it 'creates a build_info record' do
+ expect { subject }.to change { Packages::BuildInfo.count }.by(1)
+ end
+
+ it 'creates a package_file_build_info record' do
+ expect { subject }.to change { Packages::PackageFileBuildInfo.count }.by(1)
+ end
+ end
+end
+
RSpec.shared_examples 'uploads a package file' do
context 'file size above maximum limit' do
before do
diff --git a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
index 5145880ef9a..54f4ba7ff73 100644
--- a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
@@ -47,18 +47,12 @@ RSpec.shared_examples 'group and project boards query' do
describe 'sorting and pagination' do
let(:data_path) { [board_parent_type, :boards] }
- def pagination_query(params, page_info)
- graphql_query_for(
- board_parent_type,
- { 'fullPath' => board_parent.full_path },
- query_graphql_field('boards', params, "#{page_info} edges { node { id } }")
+ def pagination_query(params)
+ graphql_query_for(board_parent_type, { full_path: board_parent.full_path },
+ query_nodes(:boards, :id, include_pagination_info: true, args: params)
)
end
- def pagination_results_data(data)
- data.map { |board| board.dig('node', 'id') }
- end
-
context 'when using default sorting' do
let!(:board_B) { create(:board, resource_parent: board_parent, name: 'B') }
let!(:board_C) { create(:board, resource_parent: board_parent, name: 'C') }
@@ -72,9 +66,9 @@ RSpec.shared_examples 'group and project boards query' do
let(:first_param) { 2 }
let(:expected_results) do
if board_parent.multiple_issue_boards_available?
- boards.map { |board| board.to_global_id.to_s }
+ boards.map { |board| global_id_of(board) }
else
- [boards.first.to_global_id.to_s]
+ [global_id_of(boards.first)]
end
end
end
diff --git a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb
new file mode 100644
index 00000000000..f808d12baf4
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb
@@ -0,0 +1,265 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'handling nuget service requests' do
+ subject { get api(url) }
+
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ context 'personal token' do
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ context 'with job token' do
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') }
+ let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+end
+
+RSpec.shared_examples 'handling nuget metadata requests with package name' do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) }
+ let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
+
+ subject { get api(url) }
+
+ before do
+ packages.each { |pkg| create_dependencies_for(pkg) }
+ end
+
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name level' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+end
+
+RSpec.shared_examples 'handling nuget metadata requests with package name and package version' do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) }
+ let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
+
+ subject { get api(url) }
+
+ before do
+ create_dependencies_for(package)
+ end
+
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ context 'with invalid package name' do
+ let_it_be(:package_name) { 'Unkown' }
+
+ it_behaves_like 'rejects nuget packages access', :developer, :not_found
+ end
+end
+
+RSpec.shared_examples 'handling nuget search requests' do
+ let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) }
+ let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') }
+ let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) }
+ let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) }
+ let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) }
+ let_it_be(:package_e) { create(:nuget_package, name: 'Foo.BarE', project: project) }
+ let(:search_term) { 'uMmy' }
+ let(:take) { 26 }
+ let(:skip) { 0 }
+ let(:include_prereleases) { true }
+ let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } }
+
+ subject { get api(url) }
+
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget search request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget search request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget search request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+end
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
index 58e99776fd9..dc6ac5f0371 100644
--- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -12,7 +12,7 @@ RSpec.shared_examples 'rejects nuget packages access' do |user_type, status, add
it 'has the correct response header' do
subject
- expect(response.headers['Www-Authenticate: Basic realm']).to eq 'GitLab Packages Registry'
+ expect(response.headers['WWW-Authenticate']).to eq 'Basic realm="GitLab Packages Registry"'
end
end
end
@@ -26,7 +26,7 @@ RSpec.shared_examples 'process nuget service index request' do |user_type, statu
it_behaves_like 'returning response status', status
- it_behaves_like 'a package tracking event', described_class.name, 'cli_metadata'
+ it_behaves_like 'a package tracking event', 'API::NugetPackages', 'cli_metadata'
it 'returns a valid json response' do
subject
@@ -169,7 +169,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
context 'with correct params' do
it_behaves_like 'package workhorse uploads'
it_behaves_like 'creates nuget package files'
- it_behaves_like 'a package tracking event', described_class.name, 'push_package'
+ it_behaves_like 'a package tracking event', 'API::NugetPackages', 'push_package'
end
end
@@ -286,7 +286,7 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st
it_behaves_like 'returning response status', status
- it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
+ it_behaves_like 'a package tracking event', 'API::NugetPackages', 'pull_package'
it 'returns a valid package archive' do
subject
@@ -336,7 +336,7 @@ RSpec.shared_examples 'process nuget search request' do |user_type, status, add_
it_behaves_like 'returns a valid json search response', status, 4, [1, 5, 5, 1]
- it_behaves_like 'a package tracking event', described_class.name, 'search_package'
+ it_behaves_like 'a package tracking event', 'API::NugetPackages', 'search_package'
context 'with skip set to 2' do
let(:skip) { 2 }
diff --git a/spec/support/shared_examples/requests/graphql_shared_examples.rb b/spec/support/shared_examples/requests/graphql_shared_examples.rb
index 0045fe14501..a66bc7112fe 100644
--- a/spec/support/shared_examples/requests/graphql_shared_examples.rb
+++ b/spec/support/shared_examples/requests/graphql_shared_examples.rb
@@ -9,3 +9,8 @@ RSpec.shared_examples 'a working graphql query' do
expect(json_response.keys).to include('data')
end
end
+
+RSpec.shared_examples 'a mutation on an unauthorized resource' do
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: [::Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
+end
diff --git a/spec/support/shared_examples/requests/lfs_http_shared_examples.rb b/spec/support/shared_examples/requests/lfs_http_shared_examples.rb
index 4ae77179527..294ceffd77b 100644
--- a/spec/support/shared_examples/requests/lfs_http_shared_examples.rb
+++ b/spec/support/shared_examples/requests/lfs_http_shared_examples.rb
@@ -65,12 +65,19 @@ end
RSpec.shared_examples 'LFS http requests' do
include LfsHttpHelpers
+ let(:lfs_enabled) { true }
let(:authorize_guest) {}
let(:authorize_download) {}
let(:authorize_upload) {}
let(:lfs_object) { create(:lfs_object, :with_file) }
let(:sample_oid) { lfs_object.oid }
+ let(:sample_size) { lfs_object.size }
+ let(:sample_object) { { 'oid' => sample_oid, 'size' => sample_size } }
+ let(:non_existing_object_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
+ let(:non_existing_object_size) { 1575078 }
+ let(:non_existing_object) { { 'oid' => non_existing_object_oid, 'size' => non_existing_object_size } }
+ let(:multiple_objects) { [sample_object, non_existing_object] }
let(:authorization) { authorize_user }
let(:headers) do
@@ -89,13 +96,11 @@ RSpec.shared_examples 'LFS http requests' do
end
before do
- stub_lfs_setting(enabled: true)
+ stub_lfs_setting(enabled: lfs_enabled)
end
context 'when LFS is disabled globally' do
- before do
- stub_lfs_setting(enabled: false)
- end
+ let(:lfs_enabled) { false }
describe 'download request' do
before do
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
index d4ee68309ff..5d300d38e4a 100644
--- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -23,6 +23,11 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
end
+ after do
+ stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', nil)
+ Gitlab::RackAttack.configure_user_allowlist
+ end
+
context 'when the throttle is enabled' do
before do
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
@@ -30,6 +35,8 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
end
it 'rejects requests over the rate limit' do
+ expect(Gitlab::Instrumentation::Throttle).not_to receive(:safelist=)
+
# At first, allow requests under the rate limit.
requests_per_period.times do
make_request(request_args)
@@ -40,6 +47,18 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
expect_rejection { make_request(request_args) }
end
+ it 'does not reject requests if the user is in the allowlist' do
+ stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', user.id.to_s)
+ Gitlab::RackAttack.configure_user_allowlist
+
+ expect(Gitlab::Instrumentation::Throttle).to receive(:safelist=).with('throttle_user_allowlist').at_least(:once)
+
+ (requests_per_period + 1).times do
+ make_request(request_args)
+ expect(response).not_to have_gitlab_http_status(:too_many_requests)
+ end
+ end
+
it 'allows requests after throttling and then waiting for the next period' do
requests_per_period.times do
make_request(request_args)
@@ -110,6 +129,14 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
expect { make_request(request_args) }.not_to exceed_query_limit(control_count)
end
end
+
+ it_behaves_like 'tracking when dry-run mode is set' do
+ let(:throttle_name) { throttle_types[throttle_setting_prefix] }
+
+ def do_request
+ make_request(request_args)
+ end
+ end
end
context 'when the throttle is disabled' do
@@ -159,6 +186,11 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
end
+ after do
+ stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', nil)
+ Gitlab::RackAttack.configure_user_allowlist
+ end
+
context 'when the throttle is enabled' do
before do
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
@@ -166,6 +198,8 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
end
it 'rejects requests over the rate limit' do
+ expect(Gitlab::Instrumentation::Throttle).not_to receive(:safelist=)
+
# At first, allow requests under the rate limit.
requests_per_period.times do
request_authenticated_web_url
@@ -176,6 +210,18 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
expect_rejection { request_authenticated_web_url }
end
+ it 'does not reject requests if the user is in the allowlist' do
+ stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', user.id.to_s)
+ Gitlab::RackAttack.configure_user_allowlist
+
+ expect(Gitlab::Instrumentation::Throttle).to receive(:safelist=).with('throttle_user_allowlist').at_least(:once)
+
+ (requests_per_period + 1).times do
+ request_authenticated_web_url
+ expect(response).not_to have_gitlab_http_status(:too_many_requests)
+ end
+ end
+
it 'allows requests after throttling and then waiting for the next period' do
requests_per_period.times do
request_authenticated_web_url
@@ -245,6 +291,14 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
expect { request_authenticated_web_url }.not_to exceed_query_limit(control_count)
end
+
+ it_behaves_like 'tracking when dry-run mode is set' do
+ let(:throttle_name) { throttle_types[throttle_setting_prefix] }
+
+ def do_request
+ request_authenticated_web_url
+ end
+ end
end
context 'when the throttle is disabled' do
@@ -269,3 +323,63 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
end
end
end
+
+# Requires:
+# - #do_request - This needs to be a method so the result isn't memoized
+# - throttle_name
+RSpec.shared_examples 'tracking when dry-run mode is set' do
+ let(:dry_run_config) { '*' }
+
+ # we can't use `around` here, because stub_env isn't supported outside of the
+ # example itself
+ before do
+ stub_env('GITLAB_THROTTLE_DRY_RUN', dry_run_config)
+ reset_rack_attack
+ end
+
+ after do
+ stub_env('GITLAB_THROTTLE_DRY_RUN', '')
+ reset_rack_attack
+ end
+
+ def reset_rack_attack
+ Rack::Attack.reset!
+ Rack::Attack.clear_configuration
+ Gitlab::RackAttack.configure(Rack::Attack)
+ end
+
+ it 'does not throttle the requests when `*` is configured' do
+ (1 + requests_per_period).times do
+ do_request
+ expect(response).not_to have_gitlab_http_status(:too_many_requests)
+ end
+ end
+
+ it 'logs RackAttack info into structured logs' do
+ arguments = a_hash_including({
+ message: 'Rack_Attack',
+ env: :track,
+ remote_ip: '127.0.0.1',
+ matched: throttle_name
+ })
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(arguments)
+
+ (1 + requests_per_period).times do
+ do_request
+ end
+ end
+
+ context 'when configured with the the throttled name in a list' do
+ let(:dry_run_config) do
+ "throttle_list, #{throttle_name}, other_throttle"
+ end
+
+ it 'does not throttle' do
+ (1 + requests_per_period).times do
+ do_request
+ expect(response).not_to have_gitlab_http_status(:too_many_requests)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb b/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb
index db11b1fe07d..ff87fc5d8df 100644
--- a/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb
+++ b/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb
@@ -20,6 +20,18 @@ RSpec.shared_examples 'not accessible to non-admin users' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+ context 'with authenticated admin user without admin mode' do
+ before do
+ login_as(create(:admin))
+ end
+
+ it 'redirects to enable admin mode' do
+ subject
+
+ expect(response).to redirect_to(new_admin_session_path)
+ end
+ end
end
# Requires subject and worker_class and status_api to be defined
diff --git a/spec/support/shared_examples/requests/uploads_auhorize_shared_examples.rb b/spec/support/shared_examples/requests/uploads_auhorize_shared_examples.rb
new file mode 100644
index 00000000000..9cef5cfc25e
--- /dev/null
+++ b/spec/support/shared_examples/requests/uploads_auhorize_shared_examples.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'handle uploads authorize request' do
+ before do
+ login_as(user)
+ end
+
+ describe 'POST authorize' do
+ it 'authorizes workhorse header' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).to eq(uploader_class.workhorse_local_upload_path)
+ end
+
+ it 'rejects requests that bypassed gitlab-workhorse' do
+ workhorse_headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ expect { subject }.to raise_error(JWT::DecodeError)
+ end
+
+ context 'when using remote storage' do
+ context 'when direct upload is enabled' do
+ before do
+ stub_uploads_object_storage(uploader_class, enabled: true, direct_upload: true)
+ end
+
+ it 'responds with status 200, location of file remote store and object details' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response).not_to have_key('TempPath')
+ expect(json_response['RemoteObject']).to have_key('ID')
+ expect(json_response['RemoteObject']).to have_key('GetURL')
+ expect(json_response['RemoteObject']).to have_key('StoreURL')
+ expect(json_response['RemoteObject']).to have_key('DeleteURL')
+ expect(json_response['RemoteObject']).to have_key('MultipartUpload')
+ expect(json_response['MaximumSize']).to eq(maximum_size)
+ end
+ end
+
+ context 'when direct upload is disabled' do
+ before do
+ stub_uploads_object_storage(uploader_class, enabled: true, direct_upload: false)
+ end
+
+ it 'handles as a local file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).to eq(uploader_class.workhorse_local_upload_path)
+ expect(json_response['RemoteObject']).to be_nil
+ expect(json_response['MaximumSize']).to eq(maximum_size)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/routing/git_http_routing_shared_examples.rb b/spec/support/shared_examples/routing/git_http_routing_shared_examples.rb
new file mode 100644
index 00000000000..b0e1e942d81
--- /dev/null
+++ b/spec/support/shared_examples/routing/git_http_routing_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'git repository routes' do
+ let(:params) { { repository_path: path.delete_prefix('/') } }
+ let(:container_path) { path.delete_suffix('.git') }
+
+ it 'routes Git endpoints' do
+ expect(get("#{path}/info/refs")).to route_to('repositories/git_http#info_refs', **params)
+ expect(post("#{path}/git-upload-pack")).to route_to('repositories/git_http#git_upload_pack', **params)
+ expect(post("#{path}/git-receive-pack")).to route_to('repositories/git_http#git_receive_pack', **params)
+ end
+
+ context 'requests without .git format' do
+ it 'redirects requests to /info/refs', type: :request do
+ expect(get("#{container_path}/info/refs")).to redirect_to("#{container_path}.git/info/refs")
+ expect(get("#{container_path}/info/refs?service=git-upload-pack")).to redirect_to("#{container_path}.git/info/refs?service=git-upload-pack")
+ expect(get("#{container_path}/info/refs?service=git-receive-pack")).to redirect_to("#{container_path}.git/info/refs?service=git-receive-pack")
+ end
+
+ it 'does not redirect other requests' do
+ expect(post("#{container_path}/git-upload-pack")).not_to be_routable
+ end
+ end
+
+ it 'routes LFS endpoints' do
+ oid = generate(:oid)
+
+ expect(post("#{path}/info/lfs/objects/batch")).to route_to('repositories/lfs_api#batch', **params)
+ expect(post("#{path}/info/lfs/objects")).to route_to('repositories/lfs_api#deprecated', **params)
+ expect(get("#{path}/info/lfs/objects/#{oid}")).to route_to('repositories/lfs_api#deprecated', oid: oid, **params)
+
+ expect(post("#{path}/info/lfs/locks/123/unlock")).to route_to('repositories/lfs_locks_api#unlock', id: '123', **params)
+ expect(post("#{path}/info/lfs/locks/verify")).to route_to('repositories/lfs_locks_api#verify', **params)
+
+ expect(get("#{path}/gitlab-lfs/objects/#{oid}")).to route_to('repositories/lfs_storage#download', oid: oid, **params)
+ expect(put("#{path}/gitlab-lfs/objects/#{oid}/456/authorize")).to route_to('repositories/lfs_storage#upload_authorize', oid: oid, size: '456', **params)
+ expect(put("#{path}/gitlab-lfs/objects/#{oid}/456")).to route_to('repositories/lfs_storage#upload_finalize', oid: oid, size: '456', **params)
+
+ expect(put("#{path}/gitlab-lfs/objects/foo")).not_to be_routable
+ expect(put("#{path}/gitlab-lfs/objects/#{oid}/foo")).not_to be_routable
+ expect(put("#{path}/gitlab-lfs/objects/#{oid}/foo/authorize")).not_to be_routable
+ end
+end
diff --git a/spec/support/shared_examples/services/alert_management_shared_examples.rb b/spec/support/shared_examples/services/alert_management_shared_examples.rb
index 003705ca21c..d9f28a97a0f 100644
--- a/spec/support/shared_examples/services/alert_management_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb
@@ -16,6 +16,38 @@ RSpec.shared_examples 'creates an alert management alert' do
end
end
+# This shared_example requires the following variables:
+# - last_alert_attributes, last created alert
+# - project, project that alert created
+# - payload_raw, hash representation of payload
+# - environment, project's environment
+# - fingerprint, fingerprint hash
+RSpec.shared_examples 'assigns the alert properties' do
+ it 'ensures that created alert has all data properly assigned' do
+ subject
+
+ expect(last_alert_attributes).to match(
+ project_id: project.id,
+ title: payload_raw.fetch(:title),
+ started_at: Time.zone.parse(payload_raw.fetch(:start_time)),
+ severity: payload_raw.fetch(:severity),
+ status: AlertManagement::Alert.status_value(:triggered),
+ events: 1,
+ domain: domain,
+ hosts: payload_raw.fetch(:hosts),
+ payload: payload_raw.with_indifferent_access,
+ issue_id: nil,
+ description: payload_raw.fetch(:description),
+ monitoring_tool: payload_raw.fetch(:monitoring_tool),
+ service: payload_raw.fetch(:service),
+ fingerprint: Digest::SHA1.hexdigest(fingerprint),
+ environment_id: environment.id,
+ ended_at: nil,
+ prometheus_alert_id: nil
+ )
+ end
+end
+
RSpec.shared_examples 'does not an create alert management alert' do
it 'does not create alert' do
expect { subject }.not_to change(AlertManagement::Alert, :count)
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
new file mode 100644
index 00000000000..ba176b616c3
--- /dev/null
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -0,0 +1,1006 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'container registry auth service context' do
+ let(:current_project) { nil }
+ let(:current_user) { nil }
+ let(:current_params) { {} }
+ let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
+ let(:payload) { JWT.decode(subject[:token], rsa_key, true, { algorithm: 'RS256' }).first }
+
+ let(:authentication_abilities) do
+ [:read_container_image, :create_container_image, :admin_container_image]
+ end
+
+ let(:log_data) { { message: 'Denied container registry permissions' } }
+
+ subject do
+ described_class.new(current_project, current_user, current_params)
+ .execute(authentication_abilities: authentication_abilities)
+ end
+
+ before do
+ allow(Gitlab.config.registry).to receive_messages(enabled: true, issuer: 'rspec', key: nil)
+ allow_next_instance_of(JSONWebToken::RSAToken) do |instance|
+ allow(instance).to receive(:key).and_return(rsa_key)
+ end
+ end
+end
+
+RSpec.shared_examples 'an authenticated' do
+ it { is_expected.to include(:token) }
+ it { expect(payload).to include('access') }
+end
+
+RSpec.shared_examples 'a valid token' do
+ it { is_expected.to include(:token) }
+ it { expect(payload).to include('access') }
+
+ context 'a expirable' do
+ let(:expires_at) { Time.zone.at(payload['exp']) }
+ let(:expire_delay) { 10 }
+
+ context 'for default configuration' do
+ it { expect(expires_at).not_to be_within(2.seconds).of(Time.current + expire_delay.minutes) }
+ end
+
+ context 'for changed configuration' do
+ before do
+ stub_application_setting(container_registry_token_expire_delay: expire_delay)
+ end
+
+ it { expect(expires_at).to be_within(2.seconds).of(Time.current + expire_delay.minutes) }
+ end
+ end
+end
+
+RSpec.shared_examples 'a browsable' do
+ let(:access) do
+ [{ 'type' => 'registry',
+ 'name' => 'catalog',
+ 'actions' => ['*'] }]
+ end
+
+ it_behaves_like 'a valid token'
+ it_behaves_like 'not a container repository factory'
+
+ it 'has the correct scope' do
+ expect(payload).to include('access' => access)
+ end
+end
+
+RSpec.shared_examples 'an accessible' do
+ let(:access) do
+ [{ 'type' => 'repository',
+ 'name' => project.full_path,
+ 'actions' => actions }]
+ end
+
+ it_behaves_like 'a valid token'
+
+ it 'has the correct scope' do
+ expect(payload).to include('access' => access)
+ end
+end
+
+RSpec.shared_examples 'an inaccessible' do
+ it_behaves_like 'a valid token'
+ it { expect(payload).to include('access' => []) }
+end
+
+RSpec.shared_examples 'a deletable' do
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['*'] }
+ end
+end
+
+RSpec.shared_examples 'a deletable since registry 2.7' do
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['delete'] }
+ end
+end
+
+RSpec.shared_examples 'a pullable' do
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['pull'] }
+ end
+end
+
+RSpec.shared_examples 'a pushable' do
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['push'] }
+ end
+end
+
+RSpec.shared_examples 'a pullable and pushable' do
+ it_behaves_like 'an accessible' do
+ let(:actions) { %w(pull push) }
+ end
+end
+
+RSpec.shared_examples 'a forbidden' do
+ it { is_expected.to include(http_status: 403) }
+ it { is_expected.not_to include(:token) }
+end
+
+RSpec.shared_examples 'container repository factory' do
+ it 'creates a new container repository resource' do
+ expect { subject }
+ .to change { project.container_repositories.count }.by(1)
+ end
+end
+
+RSpec.shared_examples 'not a container repository factory' do
+ it 'does not create a new container repository resource' do
+ expect { subject }.not_to change { ContainerRepository.count }
+ end
+end
+
+RSpec.shared_examples 'logs an auth warning' do |requested_actions|
+ let(:expected) do
+ {
+ scope_type: 'repository',
+ requested_project_path: project.full_path,
+ requested_actions: requested_actions,
+ authorized_actions: [],
+ user_id: current_user.id,
+ username: current_user.username
+ }
+ end
+
+ it do
+ expect(Gitlab::AuthLogger).to receive(:warn).with(expected.merge!(log_data))
+
+ subject
+ end
+end
+
+RSpec.shared_examples 'a container registry auth service' do
+ include_context 'container registry auth service context'
+
+ describe '#full_access_token' do
+ let_it_be(:project) { create(:project) }
+ let(:token) { described_class.full_access_token(project.full_path) }
+
+ subject { { token: token } }
+
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['*'] }
+ end
+
+ it_behaves_like 'not a container repository factory'
+ end
+
+ describe '#pull_access_token' do
+ let_it_be(:project) { create(:project) }
+ let(:token) { described_class.pull_access_token(project.full_path) }
+
+ subject { { token: token } }
+
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['pull'] }
+ end
+
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'user authorization' do
+ let_it_be(:current_user) { create(:user) }
+
+ context 'for registry catalog' do
+ let(:current_params) do
+ { scopes: ["registry:catalog:*"] }
+ end
+
+ context 'disallow browsing for users without GitLab admin rights' do
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
+ context 'for private project' do
+ let_it_be(:project) { create(:project) }
+
+ context 'allow to use scope-less authentication' do
+ it_behaves_like 'a valid token'
+ end
+
+ context 'allow developer to push images' do
+ before_all do
+ project.add_developer(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push"] }
+ end
+
+ it_behaves_like 'a pushable'
+ it_behaves_like 'container repository factory'
+ end
+
+ context 'disallow developer to delete images' do
+ before_all do
+ project.add_developer(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:*"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+
+ it_behaves_like 'logs an auth warning', ['*']
+ end
+
+ context 'disallow developer to delete images since registry 2.7' do
+ before_all do
+ project.add_developer(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'allow reporter to pull images' do
+ before_all do
+ project.add_reporter(current_user)
+ end
+
+ context 'when pulling from root level repository' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull"] }
+ end
+
+ it_behaves_like 'a pullable'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
+ context 'disallow reporter to delete images' do
+ before_all do
+ project.add_reporter(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:*"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow reporter to delete images since registry 2.7' do
+ before_all do
+ project.add_reporter(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'return a least of privileges' do
+ before_all do
+ project.add_reporter(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push,pull"] }
+ end
+
+ it_behaves_like 'a pullable'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow guest to pull or push images' do
+ before_all do
+ project.add_guest(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull,push"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow guest to delete images' do
+ before_all do
+ project.add_guest(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:*"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow guest to delete images since registry 2.7' do
+ before_all do
+ project.add_guest(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
+ context 'for public project' do
+ let_it_be(:project) { create(:project, :public) }
+
+ context 'allow anyone to pull images' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull"] }
+ end
+
+ it_behaves_like 'a pullable'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow anyone to push images' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow anyone to delete images' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:*"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow anyone to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'when repository name is invalid' do
+ let(:current_params) do
+ { scopes: ['repository:invalid:push'] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
+ context 'for internal project' do
+ let_it_be(:project) { create(:project, :internal) }
+
+ context 'for internal user' do
+ context 'allow anyone to pull images' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull"] }
+ end
+
+ it_behaves_like 'a pullable'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow anyone to push images' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow anyone to delete images' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:*"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow anyone to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
+ context 'for external user' do
+ context 'disallow anyone to pull or push images' do
+ let_it_be(:current_user) { create(:user, external: true) }
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull,push"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow anyone to delete images' do
+ let_it_be(:current_user) { create(:user, external: true) }
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:*"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'disallow anyone to delete images since registry 2.7' do
+ let_it_be(:current_user) { create(:user, external: true) }
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+ end
+ end
+
+ context 'delete authorized as maintainer' do
+ let_it_be(:current_project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:authentication_abilities) do
+ [:admin_container_image]
+ end
+
+ before_all do
+ current_project.add_maintainer(current_user)
+ end
+
+ it_behaves_like 'a valid token'
+
+ context 'allow to delete images' do
+ let(:current_params) do
+ { scopes: ["repository:#{current_project.full_path}:*"] }
+ end
+
+ it_behaves_like 'a deletable' do
+ let(:project) { current_project }
+ end
+ end
+
+ context 'allow to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{current_project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'a deletable since registry 2.7' do
+ let(:project) { current_project }
+ end
+ end
+ end
+
+ context 'build authorized as user' do
+ let_it_be(:current_project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:authentication_abilities) do
+ [:build_read_container_image, :build_create_container_image, :build_destroy_container_image]
+ end
+
+ before_all do
+ current_project.add_developer(current_user)
+ end
+
+ context 'allow to use offline_token' do
+ let(:current_params) do
+ { offline_token: true }
+ end
+
+ it_behaves_like 'an authenticated'
+ end
+
+ it_behaves_like 'a valid token'
+
+ context 'allow to pull and push images' do
+ let(:current_params) do
+ { scopes: ["repository:#{current_project.full_path}:pull,push"] }
+ end
+
+ it_behaves_like 'a pullable and pushable' do
+ let(:project) { current_project }
+ end
+
+ it_behaves_like 'container repository factory' do
+ let(:project) { current_project }
+ end
+ end
+
+ context 'allow to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{current_project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'a deletable since registry 2.7' do
+ let(:project) { current_project }
+ end
+ end
+
+ context 'disallow to delete images' do
+ let(:current_params) do
+ { scopes: ["repository:#{current_project.full_path}:*"] }
+ end
+
+ it_behaves_like 'an inaccessible' do
+ let(:project) { current_project }
+ end
+ end
+
+ context 'for other projects' do
+ context 'when pulling' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull"] }
+ end
+
+ context 'allow for public' do
+ let_it_be(:project) { create(:project, :public) }
+
+ it_behaves_like 'a pullable'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ shared_examples 'pullable for being team member' do
+ context 'when you are not member' do
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'when you are member' do
+ before_all do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a pullable'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'when you are owner' do
+ let_it_be(:project) { create(:project, namespace: current_user.namespace) }
+
+ it_behaves_like 'a pullable'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
+ context 'for private' do
+ let_it_be(:project) { create(:project, :private) }
+
+ it_behaves_like 'pullable for being team member'
+
+ context 'when you are admin' do
+ let_it_be(:current_user) { create(:admin) }
+
+ context 'when you are not member' do
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'when you are member' do
+ before_all do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a pullable'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'when you are owner' do
+ let_it_be(:project) { create(:project, namespace: current_user.namespace) }
+
+ it_behaves_like 'a pullable'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+ end
+ end
+
+ context 'when pushing' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push"] }
+ end
+
+ context 'disallow for all' do
+ context 'when you are member' do
+ let_it_be(:project) { create(:project, :public) }
+
+ before_all do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'when you are owner' do
+ let_it_be(:project) { create(:project, :public, namespace: current_user.namespace) }
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+ end
+ end
+
+ context 'for project without container registry' do
+ let_it_be(:project) { create(:project, :public, container_registry_enabled: false) }
+
+ before do
+ project.update!(container_registry_enabled: false)
+ end
+
+ context 'disallow when pulling' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
+ context 'for project that disables repository' do
+ let_it_be(:project) { create(:project, :public, :repository_disabled) }
+
+ context 'disallow when pulling' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+ end
+
+ context 'registry catalog browsing authorized as admin' do
+ let_it_be(:current_user) { create(:user, :admin) }
+ let_it_be(:project) { create(:project, :public) }
+
+ let(:current_params) do
+ { scopes: ["registry:catalog:*"] }
+ end
+
+ it_behaves_like 'a browsable'
+ end
+
+ context 'support for multiple scopes' do
+ let_it_be(:internal_project) { create(:project, :internal) }
+ let_it_be(:private_project) { create(:project, :private) }
+
+ let(:current_params) do
+ {
+ scopes: [
+ "repository:#{internal_project.full_path}:pull",
+ "repository:#{private_project.full_path}:pull"
+ ]
+ }
+ end
+
+ context 'user has access to all projects' do
+ let_it_be(:current_user) { create(:user, :admin) }
+
+ before do
+ enable_admin_mode!(current_user)
+ end
+
+ it_behaves_like 'a browsable' do
+ let(:access) do
+ [
+ { 'type' => 'repository',
+ 'name' => internal_project.full_path,
+ 'actions' => ['pull'] },
+ { 'type' => 'repository',
+ 'name' => private_project.full_path,
+ 'actions' => ['pull'] }
+ ]
+ end
+ end
+ end
+
+ context 'user only has access to internal project' do
+ let_it_be(:current_user) { create(:user) }
+
+ it_behaves_like 'a browsable' do
+ let(:access) do
+ [
+ { 'type' => 'repository',
+ 'name' => internal_project.full_path,
+ 'actions' => ['pull'] }
+ ]
+ end
+ end
+ end
+
+ context 'anonymous access is rejected' do
+ let(:current_user) { nil }
+
+ it_behaves_like 'a forbidden'
+ end
+ end
+
+ context 'unauthorized' do
+ context 'disallow to use scope-less authentication' do
+ it_behaves_like 'a forbidden'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'for invalid scope' do
+ let(:current_params) do
+ { scopes: ['invalid:aa:bb'] }
+ end
+
+ it_behaves_like 'a forbidden'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'for private project' do
+ let_it_be(:project) { create(:project, :private) }
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull"] }
+ end
+
+ it_behaves_like 'a forbidden'
+ end
+
+ context 'for public project' do
+ let_it_be(:project) { create(:project, :public) }
+
+ context 'when pulling and pushing' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull,push"] }
+ end
+
+ it_behaves_like 'a pullable'
+ it_behaves_like 'not a container repository factory'
+ end
+
+ context 'when pushing' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push"] }
+ end
+
+ it_behaves_like 'a forbidden'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
+ context 'for registry catalog' do
+ let(:current_params) do
+ { scopes: ["registry:catalog:*"] }
+ end
+
+ it_behaves_like 'a forbidden'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
+ context 'for deploy tokens' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull"] }
+ end
+
+ context 'when deploy token has read and write registry as scopes' do
+ let(:current_user) { create(:deploy_token, write_registry: true, projects: [project]) }
+
+ shared_examples 'able to login' do
+ context 'registry provides read_container_image authentication_abilities' do
+ let(:current_params) { {} }
+ let(:authentication_abilities) { [:read_container_image] }
+
+ it_behaves_like 'an authenticated'
+ end
+ end
+
+ context 'for public project' do
+ let_it_be(:project) { create(:project, :public) }
+
+ context 'when pulling' do
+ it_behaves_like 'a pullable'
+ end
+
+ context 'when pushing' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push"] }
+ end
+
+ it_behaves_like 'a pushable'
+ end
+
+ it_behaves_like 'able to login'
+ end
+
+ context 'for internal project' do
+ let_it_be(:project) { create(:project, :internal) }
+
+ context 'when pulling' do
+ it_behaves_like 'a pullable'
+ end
+
+ context 'when pushing' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push"] }
+ end
+
+ it_behaves_like 'a pushable'
+ end
+
+ it_behaves_like 'able to login'
+ end
+
+ context 'for private project' do
+ let_it_be(:project) { create(:project, :private) }
+
+ context 'when pulling' do
+ it_behaves_like 'a pullable'
+ end
+
+ context 'when pushing' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push"] }
+ end
+
+ it_behaves_like 'a pushable'
+ end
+
+ it_behaves_like 'able to login'
+ end
+ end
+
+ context 'when deploy token does not have read_registry scope' do
+ let(:current_user) { create(:deploy_token, projects: [project], read_registry: false) }
+
+ shared_examples 'unable to login' do
+ context 'registry provides no container authentication_abilities' do
+ let(:current_params) { {} }
+ let(:authentication_abilities) { [] }
+
+ it_behaves_like 'a forbidden'
+ end
+
+ context 'registry provides inapplicable container authentication_abilities' do
+ let(:current_params) { {} }
+ let(:authentication_abilities) { [:download_code] }
+
+ it_behaves_like 'a forbidden'
+ end
+ end
+
+ context 'for public project' do
+ let_it_be(:project) { create(:project, :public) }
+
+ context 'when pulling' do
+ it_behaves_like 'a pullable'
+ end
+
+ it_behaves_like 'unable to login'
+ end
+
+ context 'for internal project' do
+ let_it_be(:project) { create(:project, :internal) }
+
+ context 'when pulling' do
+ it_behaves_like 'an inaccessible'
+ end
+
+ it_behaves_like 'unable to login'
+ end
+
+ context 'for private project' do
+ let_it_be(:project) { create(:project, :internal) }
+
+ context 'when pulling' do
+ it_behaves_like 'an inaccessible'
+ end
+
+ context 'when logging in' do
+ let(:current_params) { {} }
+ let(:authentication_abilities) { [] }
+
+ it_behaves_like 'a forbidden'
+ end
+
+ it_behaves_like 'unable to login'
+ end
+ end
+
+ context 'when deploy token is not related to the project' do
+ let_it_be(:current_user) { create(:deploy_token, read_registry: false) }
+
+ context 'for public project' do
+ let_it_be(:project) { create(:project, :public) }
+
+ context 'when pulling' do
+ it_behaves_like 'a pullable'
+ end
+ end
+
+ context 'for internal project' do
+ let_it_be(:project) { create(:project, :internal) }
+
+ context 'when pulling' do
+ it_behaves_like 'an inaccessible'
+ end
+ end
+
+ context 'for private project' do
+ let_it_be(:project) { create(:project, :internal) }
+
+ context 'when pulling' do
+ it_behaves_like 'an inaccessible'
+ end
+ end
+ end
+
+ context 'when deploy token has been revoked' do
+ let(:current_user) { create(:deploy_token, :revoked, projects: [project]) }
+
+ context 'for public project' do
+ let_it_be(:project) { create(:project, :public) }
+
+ it_behaves_like 'a pullable'
+ end
+
+ context 'for internal project' do
+ let_it_be(:project) { create(:project, :internal) }
+
+ it_behaves_like 'an inaccessible'
+ end
+
+ context 'for private project' do
+ let_it_be(:project) { create(:project, :internal) }
+
+ it_behaves_like 'an inaccessible'
+ end
+ end
+ end
+
+ context 'user authorization' do
+ let_it_be(:current_user) { create(:user) }
+
+ context 'with multiple scopes' do
+ let_it_be(:project) { create(:project) }
+
+ context 'allow developer to push images' do
+ before_all do
+ project.add_developer(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push"] }
+ end
+
+ it_behaves_like 'a pushable'
+ it_behaves_like 'container repository factory'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/incident_shared_examples.rb b/spec/support/shared_examples/services/incident_shared_examples.rb
index 39c22ac8aa3..9fced12b543 100644
--- a/spec/support/shared_examples/services/incident_shared_examples.rb
+++ b/spec/support/shared_examples/services/incident_shared_examples.rb
@@ -11,11 +11,13 @@
#
# include_examples 'incident issue'
RSpec.shared_examples 'incident issue' do
- let(:label_properties) { attributes_for(:label, :incident) }
-
it 'has incident as issue type' do
expect(issue.issue_type).to eq('incident')
end
+end
+
+RSpec.shared_examples 'has incident label' do
+ let(:label_properties) { attributes_for(:label, :incident) }
it 'has exactly one incident label' do
expect(issue.labels).to be_one do |label|
diff --git a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
index 1501a2a0f52..b6c33eac7b4 100644
--- a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
+++ b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
@@ -46,7 +46,7 @@ RSpec.shared_examples 'refreshes cache when dashboard_version is changed' do
allow(service).to receive(:dashboard_version).and_return('1', '2')
end
- expect(File).to receive(:read).twice.and_call_original
+ expect_file_read(Rails.root.join(described_class::DASHBOARD_PATH)).twice.and_call_original
service = described_class.new(*service_params)
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
index 7987f2c296b..70d29b4bc99 100644
--- a/spec/support/shared_examples/services/packages_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -14,6 +14,24 @@ RSpec.shared_examples 'assigns build to package' do
end
end
+RSpec.shared_examples 'assigns build to package file' do
+ context 'with build info' do
+ let(:job) { create(:ci_build, user: user) }
+ let(:params) { super().merge(build: job) }
+
+ it 'assigns the pipeline to the package' do
+ package_file = subject
+
+ expect(package_file.package_file_build_infos).to be_present
+ expect(package_file.pipelines.first).to eq job.pipeline
+ end
+
+ it 'creates a new PackageFileBuildInfo record' do
+ expect { subject }.to change { Packages::PackageFileBuildInfo.count }.by(1)
+ end
+ end
+end
+
RSpec.shared_examples 'assigns the package creator' do
it 'assigns the package creator' do
subject
diff --git a/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb b/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
index 37f44f98cda..1b09b5fe613 100644
--- a/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
+++ b/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
@@ -10,6 +10,10 @@ RSpec.shared_examples 'reenqueuer' do
expect(subject.lease_timeout).to be_a(ActiveSupport::Duration)
end
+ it 'uses the :none deduplication strategy' do
+ expect(subject.class.get_deduplicate_strategy).to eq(:none)
+ end
+
describe '#perform' do
it 'tries to obtain a lease' do
expect_to_obtain_exclusive_lease(subject.lease_key)
diff --git a/spec/support/shared_examples/workers/project_export_shared_examples.rb b/spec/support/shared_examples/workers/project_export_shared_examples.rb
new file mode 100644
index 00000000000..a9bcc3f4f7c
--- /dev/null
+++ b/spec/support/shared_examples/workers/project_export_shared_examples.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'export worker' do
+ describe '#perform' do
+ let!(:user) { create(:user) }
+ let!(:project) { create(:project) }
+
+ before do
+ allow_next_instance_of(described_class) do |job|
+ allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
+ end
+ end
+
+ context 'when it succeeds' do
+ it 'calls the ExportService' do
+ expect_next_instance_of(::Projects::ImportExport::ExportService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject.perform(user.id, project.id, { 'klass' => 'Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy' })
+ end
+
+ context 'export job' do
+ before do
+ allow_next_instance_of(::Projects::ImportExport::ExportService) do |service|
+ allow(service).to receive(:execute)
+ end
+ end
+
+ it 'creates an export job record for the project' do
+ expect { subject.perform(user.id, project.id, {}) }.to change { project.export_jobs.count }.from(0).to(1)
+ end
+
+ it 'sets the export job status to started' do
+ expect_next_instance_of(ProjectExportJob) do |job|
+ expect(job).to receive(:start)
+ end
+
+ subject.perform(user.id, project.id, {})
+ end
+
+ it 'sets the export job status to finished' do
+ expect_next_instance_of(ProjectExportJob) do |job|
+ expect(job).to receive(:finish)
+ end
+
+ subject.perform(user.id, project.id, {})
+ end
+ end
+ end
+
+ context 'when it fails' do
+ it 'does not raise an exception when strategy is invalid' do
+ expect(::Projects::ImportExport::ExportService).not_to receive(:new)
+
+ expect { subject.perform(user.id, project.id, { 'klass' => 'Whatever' }) }.not_to raise_error
+ end
+
+ it 'does not raise error when project cannot be found' do
+ expect { subject.perform(user.id, non_existing_record_id, {}) }.not_to raise_error
+ end
+
+ it 'does not raise error when user cannot be found' do
+ expect { subject.perform(non_existing_record_id, project.id, {}) }.not_to raise_error
+ end
+ end
+ end
+
+ describe 'sidekiq options' do
+ it 'disables retry' do
+ expect(described_class.sidekiq_options['retry']).to eq(false)
+ end
+
+ it 'disables dead' do
+ expect(described_class.sidekiq_options['dead']).to eq(false)
+ end
+
+ it 'sets default status expiration' do
+ expect(described_class.sidekiq_options['status_expiration']).to eq(StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION)
+ end
+ end
+end
diff --git a/spec/support/snowplow.rb b/spec/support/snowplow.rb
index b67fa96fab8..0d6102f1705 100644
--- a/spec/support/snowplow.rb
+++ b/spec/support/snowplow.rb
@@ -9,12 +9,15 @@ RSpec.configure do |config|
# WebMock is set up to allow requests to `localhost`
host = 'localhost'
+ allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event)
+
allow_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
.to receive(:emitter)
.and_return(SnowplowTracker::Emitter.new(host, buffer_size: buffer_size))
stub_application_setting(snowplow_enabled: true)
+ allow(SnowplowTracker::SelfDescribingJson).to receive(:new).and_call_original
allow(Gitlab::Tracking).to receive(:event).and_call_original # rubocop:disable RSpec/ExpectGitlabTracking
end
diff --git a/spec/support_specs/graphql/arguments_spec.rb b/spec/support_specs/graphql/arguments_spec.rb
new file mode 100644
index 00000000000..ffb58503a0e
--- /dev/null
+++ b/spec/support_specs/graphql/arguments_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Graphql::Arguments do
+ it 'returns a blank string if the arguments are blank' do
+ args = described_class.new({})
+
+ expect("#{args}").to be_blank
+ end
+
+ it 'returns a serialized arguments if the arguments are not blank' do
+ units = described_class.new({ temp: :CELSIUS, time: :MINUTES })
+ args = described_class.new({ temp: 180, time: 45, units: units })
+
+ expect("#{args}").to eq('temp: 180, time: 45, units: {temp: CELSIUS, time: MINUTES}')
+ end
+
+ it 'supports merge with +' do
+ lhs = described_class.new({ a: 1, b: 2 })
+ rhs = described_class.new({ b: 3, c: 4 })
+
+ expect(lhs + rhs).to eq({ a: 1, b: 3, c: 4 })
+ end
+
+ it 'supports merge with + and a string' do
+ lhs = described_class.new({ a: 1, b: 2 })
+ rhs = 'x: no'
+
+ expect(lhs + rhs).to eq('a: 1, b: 2, x: no')
+ end
+
+ it 'supports merge with + and a string when empty' do
+ lhs = described_class.new({})
+ rhs = 'x: no'
+
+ expect(lhs + rhs).to eq('x: no')
+ end
+
+ it 'supports merge with + and an empty string' do
+ lhs = described_class.new({ a: 1 })
+ rhs = ''
+
+ expect(lhs + rhs).to eq({ a: 1 })
+ end
+
+ it 'serializes all values correctly' do
+ args = described_class.new({
+ array: [1, 2.5, "foo", nil, true, false, :BAR, { power: :on }],
+ hash: { a: 1, b: 2, c: 3 },
+ int: 42,
+ float: 2.7,
+ string: %q[he said "no"],
+ enum: :OFF,
+ null: nil, # we expect this to be omitted - absence is the same as explicit nullness
+ bool_true: true,
+ bool_false: false,
+ var: ::Graphql::Var.new('x', 'Int')
+ })
+
+ expect(args.to_s).to eq([
+ %q(array: [1,2.5,"foo",null,true,false,BAR,{power: on}]),
+ %q(hash: {a: 1, b: 2, c: 3}),
+ 'int: 42, float: 2.7',
+ %q(string: "he said \\"no\\""),
+ 'enum: OFF',
+ 'boolTrue: true, boolFalse: false',
+ 'var: $x'
+ ].join(', '))
+ end
+end
diff --git a/spec/support_specs/graphql/field_selection_spec.rb b/spec/support_specs/graphql/field_selection_spec.rb
new file mode 100644
index 00000000000..8818e59e598
--- /dev/null
+++ b/spec/support_specs/graphql/field_selection_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Graphql::FieldSelection do
+ it 'can report on the paths that are selected' do
+ selection = described_class.new({
+ 'foo' => nil,
+ 'bar' => nil,
+ 'quux' => {
+ 'a' => nil,
+ 'b' => { 'x' => nil, 'y' => nil }
+ },
+ 'qoox' => {
+ 'q' => nil,
+ 'r' => { 's' => { 't' => nil } }
+ }
+ })
+
+ expect(selection.paths).to include(
+ %w[foo],
+ %w[quux a],
+ %w[quux b x],
+ %w[qoox r s t]
+ )
+ end
+
+ it 'can serialize a field selection nicely' do
+ selection = described_class.new({
+ 'foo' => nil,
+ 'bar' => nil,
+ 'quux' => {
+ 'a' => nil,
+ 'b' => { 'x' => nil, 'y' => nil }
+ },
+ 'qoox' => {
+ 'q' => nil,
+ 'r' => { 's' => { 't' => nil } }
+ }
+ })
+
+ expect(selection.to_s).to eq(<<~FRAG.strip)
+ foo
+ bar
+ quux {
+ a
+ b {
+ x
+ y
+ }
+ }
+ qoox {
+ q
+ r {
+ s {
+ t
+ }
+ }
+ }
+ FRAG
+ end
+end
diff --git a/spec/support_specs/graphql/var_spec.rb b/spec/support_specs/graphql/var_spec.rb
new file mode 100644
index 00000000000..f708f01a11e
--- /dev/null
+++ b/spec/support_specs/graphql/var_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Graphql::Var do
+ subject(:var) { described_class.new('foo', 'Int') }
+
+ it 'associates a name with a type and an initially empty value' do
+ expect(var).to have_attributes(
+ name: 'foo',
+ type: 'Int',
+ value: be_nil
+ )
+ end
+
+ it 'has a correct signature' do
+ expect(var).to have_attributes(sig: '$foo: Int')
+ end
+
+ it 'implements to_graphql_value as $name' do
+ expect(var.to_graphql_value).to eq('$foo')
+ end
+
+ it 'can set a value using with, returning a new object' do
+ with_value = var.with(42)
+
+ expect(with_value).to have_attributes(name: 'foo', type: 'Int', value: 42)
+ expect(var).to have_attributes(value: be_nil)
+ end
+
+ it 'returns an object suitable for passing to post_graphql(variables:)' do
+ expect(var.with(17).to_h).to eq('foo' => 17)
+ end
+end
diff --git a/spec/support_specs/helpers/graphql_helpers_spec.rb b/spec/support_specs/helpers/graphql_helpers_spec.rb
index bc777621674..a9fe5b8d196 100644
--- a/spec/support_specs/helpers/graphql_helpers_spec.rb
+++ b/spec/support_specs/helpers/graphql_helpers_spec.rb
@@ -5,6 +5,278 @@ require 'spec_helper'
RSpec.describe GraphqlHelpers do
include GraphqlHelpers
+ # Normalize irrelevant whitespace to make comparison easier
+ def norm(query)
+ query.tr("\n", ' ').gsub(/\s+/, ' ').strip
+ end
+
+ describe 'graphql_dig_at' do
+ it 'transforms symbol keys to graphql field names' do
+ data = { 'camelCased' => 'names' }
+
+ expect(graphql_dig_at(data, :camel_cased)).to eq('names')
+ end
+
+ it 'supports integer indexing' do
+ data = { 'array' => [:boom, { 'id' => :hooray! }, :boom] }
+
+ expect(graphql_dig_at(data, :array, 1, :id)).to eq(:hooray!)
+ end
+
+ it 'gracefully degrades to nil' do
+ data = { 'project' => { 'mergeRequest' => nil } }
+
+ expect(graphql_dig_at(data, :project, :merge_request, :id)).to be_nil
+ end
+
+ it 'supports implicitly flat-mapping traversals' do
+ data = {
+ 'foo' => {
+ 'nodes' => [
+ { 'bar' => { 'nodes' => [{ 'id' => 1 }, { 'id' => 2 }] } },
+ { 'bar' => { 'nodes' => [{ 'id' => 3 }, { 'id' => 4 }] } },
+ { 'bar' => nil }
+ ]
+ },
+ 'irrelevant' => 'the field is a red-herring'
+ }
+
+ expect(graphql_dig_at(data, :foo, :nodes, :bar, :nodes, :id)).to eq([1, 2, 3, 4])
+ end
+ end
+
+ describe 'var' do
+ it 'allocates a fresh name for each var' do
+ a = var('Int')
+ b = var('Int')
+
+ expect(a.name).not_to eq(b.name)
+ end
+
+ it 'can be used to construct correct signatures' do
+ a = var('Int')
+ b = var('String!')
+
+ q = with_signature([a, b], '{ foo bar }')
+
+ expect(q).to eq("query(#{a.to_graphql_value}: Int, #{b.to_graphql_value}: String!) { foo bar }")
+ end
+
+ it 'can be used to pass arguments to fields' do
+ a = var('ID!')
+
+ q = graphql_query_for(:project, { full_path: a }, :id)
+
+ expect(norm(q)).to eq("{ project(fullPath: #{a.to_graphql_value}){ id } }")
+ end
+
+ it 'can associate values with variables' do
+ a = var('Int')
+
+ expect(a.with(3).to_h).to eq(a.name => 3)
+ end
+
+ it 'does not mutate the variable when providing a value' do
+ a = var('Int')
+ three = a.with(3)
+
+ expect(three.value).to eq(3)
+ expect(a.value).to be_nil
+ end
+
+ it 'can associate many values with variables' do
+ a = var('Int').with(3)
+ b = var('String').with('foo')
+
+ expect(serialize_variables([a, b])).to eq({ a.name => 3, b.name => 'foo' }.to_json)
+ end
+ end
+
+ describe '.query_nodes' do
+ it 'can produce a basic connection selection' do
+ selection = query_nodes(:users)
+
+ expected = query_graphql_path([:users, :nodes], all_graphql_fields_for('User', max_depth: 1))
+
+ expect(selection).to eq(expected)
+ end
+
+ it 'allows greater depth' do
+ selection = query_nodes(:users, max_depth: 2)
+
+ expected = query_graphql_path([:users, :nodes], all_graphql_fields_for('User', max_depth: 2))
+
+ expect(selection).to eq(expected)
+ end
+
+ it 'accepts fields' do
+ selection = query_nodes(:users, :id)
+
+ expected = query_graphql_path([:users, :nodes], :id)
+
+ expect(selection).to eq(expected)
+ end
+
+ it 'accepts arguments' do
+ args = { username: 'foo' }
+ selection = query_nodes(:users, args: args)
+
+ expected = query_graphql_path([[:users, args], :nodes], all_graphql_fields_for('User', max_depth: 1))
+
+ expect(selection).to eq(expected)
+ end
+
+ it 'accepts arguments and fields' do
+ selection = query_nodes(:users, :id, args: { username: 'foo' })
+
+ expected = query_graphql_path([[:users, { username: 'foo' }], :nodes], :id)
+
+ expect(selection).to eq(expected)
+ end
+
+ it 'accepts explicit type name' do
+ selection = query_nodes(:members, of: 'User')
+
+ expected = query_graphql_path([:members, :nodes], all_graphql_fields_for('User', max_depth: 1))
+
+ expect(selection).to eq(expected)
+ end
+
+ it 'can optionally provide pagination info' do
+ selection = query_nodes(:users, include_pagination_info: true)
+
+ expected = query_graphql_path([:users, "#{page_info_selection} nodes"], all_graphql_fields_for('User', max_depth: 1))
+
+ expect(selection).to eq(expected)
+ end
+ end
+
+ describe '.query_graphql_path' do
+ it 'can build nested paths' do
+ selection = query_graphql_path(%i[foo bar wibble_wobble], :id)
+
+ expected = norm(<<-GQL)
+ foo{
+ bar{
+ wibbleWobble{
+ id
+ }
+ }
+ }
+ GQL
+
+ expect(norm(selection)).to eq(expected)
+ end
+
+ it 'can insert arguments at any point' do
+ selection = query_graphql_path(
+ [:foo, [:bar, { quux: true }], [:wibble_wobble, { eccentricity: :HIGH }]],
+ :id
+ )
+
+ expected = norm(<<-GQL)
+ foo{
+ bar(quux: true){
+ wibbleWobble(eccentricity: HIGH){
+ id
+ }
+ }
+ }
+ GQL
+
+ expect(norm(selection)).to eq(expected)
+ end
+ end
+
+ describe '.attributes_to_graphql' do
+ it 'can serialize hashes to literal arguments' do
+ x = var('Int')
+ args = {
+ an_array: [1, nil, "foo", true, [:foo, :bar]],
+ a_hash: {
+ nested: true,
+ value: "bar"
+ },
+ an_int: 42,
+ a_float: 0.1,
+ a_string: "wibble",
+ an_enum: :LOW,
+ null: nil,
+ a_bool: false,
+ a_var: x
+ }
+
+ literal = attributes_to_graphql(args)
+
+ expect(norm(literal)).to eq(norm(<<~EXP))
+ anArray: [1,null,"foo",true,[foo,bar]],
+ aHash: {nested: true, value: "bar"},
+ anInt: 42,
+ aFloat: 0.1,
+ aString: "wibble",
+ anEnum: LOW,
+ aBool: false,
+ aVar: #{x.to_graphql_value}
+ EXP
+ end
+ end
+
+ describe '.all_graphql_fields_for' do
+ it 'returns a FieldSelection' do
+ selection = all_graphql_fields_for('User', max_depth: 1)
+
+ expect(selection).to be_a(::Graphql::FieldSelection)
+ end
+
+ it 'returns nil if the depth is too shallow' do
+ selection = all_graphql_fields_for('User', max_depth: 0)
+
+ expect(selection).to be_nil
+ end
+
+ it 'can select just the scalar fields' do
+ selection = all_graphql_fields_for('User', max_depth: 1)
+ paths = selection.paths.map(&:join)
+
+ # A sample, tested using include to save churn as fields are added
+ expect(paths)
+ .to include(*%w[avatarUrl email groupCount id location name state username webPath webUrl])
+
+ expect(selection.paths).to all(have_attributes(size: 1))
+ end
+
+ it 'selects only as far as 3 levels by default' do
+ selection = all_graphql_fields_for('User')
+
+ expect(selection.paths).to all(have_attributes(size: (be <= 3)))
+
+ # Representative sample
+ expect(selection.paths).to include(
+ %w[userPermissions createSnippet],
+ %w[todos nodes id],
+ %w[starredProjects nodes name],
+ %w[authoredMergeRequests count],
+ %w[assignedMergeRequests pageInfo startCursor]
+ )
+ end
+
+ it 'selects only as far as requested' do
+ selection = all_graphql_fields_for('User', max_depth: 2)
+
+ expect(selection.paths).to all(have_attributes(size: (be <= 2)))
+ end
+
+ it 'omits fields that have required arguments' do
+ selection = all_graphql_fields_for('DesignCollection', max_depth: 3)
+
+ expect(selection.paths).not_to be_empty
+
+ expect(selection.paths).not_to include(
+ %w[designAtVersion id]
+ )
+ end
+ end
+
describe '.graphql_mutation' do
shared_examples 'correct mutation definition' do
it 'returns correct mutation definition' do
@@ -15,7 +287,7 @@ RSpec.describe GraphqlHelpers do
}
}
MUTATION
- variables = %q({"updateAlertStatusInput":{"projectPath":"test/project"}})
+ variables = { "updateAlertStatusInput" => { "projectPath" => "test/project" } }
is_expected.to eq(GraphqlHelpers::MutationDefinition.new(query, variables))
end
diff --git a/spec/support_specs/helpers/stub_feature_flags_spec.rb b/spec/support_specs/helpers/stub_feature_flags_spec.rb
index 57dd3015f5e..8629e895fd1 100644
--- a/spec/support_specs/helpers/stub_feature_flags_spec.rb
+++ b/spec/support_specs/helpers/stub_feature_flags_spec.rb
@@ -5,19 +5,20 @@ require 'spec_helper'
RSpec.describe StubFeatureFlags do
let_it_be(:dummy_feature_flag) { :dummy_feature_flag }
- # We inject dummy feature flag defintion
- # to ensure that we strong validate it's usage
- # as well
- before(:all) do
- definition = Feature::Definition.new(
+ let_it_be(:dummy_definition) do
+ Feature::Definition.new(
nil,
name: dummy_feature_flag,
type: 'development',
- # we allow ambigious usage of `default_enabled:`
- default_enabled: [false, true]
+ default_enabled: false
)
+ end
- Feature::Definition.definitions[dummy_feature_flag] = definition
+ # We inject dummy feature flag defintion
+ # to ensure that we strong validate it's usage
+ # as well
+ before(:all) do
+ Feature::Definition.definitions[dummy_feature_flag] = dummy_definition
end
after(:all) do
@@ -47,6 +48,10 @@ RSpec.describe StubFeatureFlags do
it { expect(Feature.disabled?(feature_name)).not_to eq(expected_result) }
context 'default_enabled does not impact feature state' do
+ before do
+ allow(dummy_definition).to receive(:default_enabled).and_return(true)
+ end
+
it { expect(Feature.enabled?(feature_name, default_enabled: true)).to eq(expected_result) }
it { expect(Feature.disabled?(feature_name, default_enabled: true)).not_to eq(expected_result) }
end
@@ -79,6 +84,10 @@ RSpec.describe StubFeatureFlags do
it { expect(Feature.disabled?(feature_name, actor(tested_actor))).not_to eq(expected_result) }
context 'default_enabled does not impact feature state' do
+ before do
+ allow(dummy_definition).to receive(:default_enabled).and_return(true)
+ end
+
it { expect(Feature.enabled?(feature_name, actor(tested_actor), default_enabled: true)).to eq(expected_result) }
it { expect(Feature.disabled?(feature_name, actor(tested_actor), default_enabled: true)).not_to eq(expected_result) }
end
diff --git a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
index 15b846f28cb..6d8d9ba0754 100644
--- a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
+++ b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
@@ -22,6 +22,232 @@ RSpec.describe ExceedQueryLimitHelpers do
end
end
+ describe '#diff_query_group_message' do
+ it 'prints a group helpfully' do
+ test_matcher = TestMatcher.new
+ suffixes = {
+ 'WHERE x = z' => [1, 1],
+ 'WHERE x = y' => [1, 2],
+ 'LIMIT 1' => [1, 0]
+ }
+
+ message = test_matcher.diff_query_group_message('SELECT * FROM foo', suffixes)
+
+ expect(message).to eq(<<~MSG.chomp)
+ SELECT * FROM foo...
+ -- (expected: 1, got: 1)
+ WHERE x = z
+ -- (expected: 1, got: 2)
+ WHERE x = y
+ -- (expected: 1, got: 0)
+ LIMIT 1
+ MSG
+ end
+ end
+
+ describe '#diff_query_counts' do
+ let(:expected) do
+ ActiveRecord::QueryRecorder.new do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.count
+ TestQueries.first
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'x').update_all(version: 'y')
+ TestQueries.where(version: 'foobar').count
+ TestQueries.where(version: 'z').delete_all
+ end
+ end
+
+ let(:actual) do
+ ActiveRecord::QueryRecorder.new do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.count
+ TestQueries.create!(version: 'x')
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'x').update_all(version: 'y')
+ TestQueries.where(version: 'foobar').count
+ TestQueries.count
+ TestQueries.where(version: 'y').update_all(version: 'z')
+ TestQueries.where(version: 'z').delete_all
+ end
+ end
+
+ it 'merges two query counts' do
+ test_matcher = TestMatcher.new
+
+ diff = test_matcher.diff_query_counts(
+ test_matcher.count_queries(expected),
+ test_matcher.count_queries(actual)
+ )
+
+ expect(diff).to eq({
+ "SELECT \"schema_migrations\".* FROM \"schema_migrations\"" => {
+ "ORDER BY \"schema_migrations\".\"version\" ASC LIMIT 1" => [1, 0]
+ },
+ "SELECT COUNT(*) FROM \"schema_migrations\"" => { "" => [1, 2] },
+ "UPDATE \"schema_migrations\"" => {
+ "SET \"version\" = 'z' WHERE \"schema_migrations\".\"version\" = 'y'" => [0, 1]
+ },
+ "SAVEPOINT active_record_1" => { "" => [0, 1] },
+ "INSERT INTO \"schema_migrations\" (\"version\")" => {
+ "VALUES ('x') RETURNING \"version\"" => [0, 1]
+ },
+ "RELEASE SAVEPOINT active_record_1" => { "" => [0, 1] }
+ })
+ end
+
+ it 'can show common queries if so desired' do
+ test_matcher = TestMatcher.new.show_common_queries
+
+ diff = test_matcher.diff_query_counts(
+ test_matcher.count_queries(expected),
+ test_matcher.count_queries(actual)
+ )
+
+ expect(diff).to eq({
+ "SELECT \"schema_migrations\".* FROM \"schema_migrations\"" => {
+ "WHERE \"schema_migrations\".\"version\" = 'foobar'" => [2, 2],
+ "WHERE \"schema_migrations\".\"version\" = 'also foobar and baz'" => [1, 1],
+ "ORDER BY \"schema_migrations\".\"version\" ASC LIMIT 1" => [1, 0]
+ },
+ "SELECT COUNT(*) FROM \"schema_migrations\"" => {
+ "" => [1, 2],
+ "WHERE \"schema_migrations\".\"version\" = 'foobar'" => [1, 1]
+ },
+ "UPDATE \"schema_migrations\"" => {
+ "SET \"version\" = 'y' WHERE \"schema_migrations\".\"version\" = 'x'" => [1, 1],
+ "SET \"version\" = 'z' WHERE \"schema_migrations\".\"version\" = 'y'" => [0, 1]
+ },
+ "DELETE FROM \"schema_migrations\"" => {
+ "WHERE \"schema_migrations\".\"version\" = 'z'" => [1, 1]
+ },
+ "SAVEPOINT active_record_1" => {
+ "" => [0, 1]
+ },
+ "INSERT INTO \"schema_migrations\" (\"version\")" => {
+ "VALUES ('x') RETURNING \"version\"" => [0, 1]
+ },
+ "RELEASE SAVEPOINT active_record_1" => {
+ "" => [0, 1]
+ }
+ })
+ end
+ end
+
+ describe '#count_queries' do
+ it 'handles queries with suffixes over multiple lines' do
+ test_matcher = TestMatcher.new
+
+ recorder = ActiveRecord::QueryRecorder.new do
+ TestQueries.find_by(version: %w(foo bar baz).join("\n"))
+ TestQueries.find_by(version: %w(foo biz baz).join("\n"))
+ TestQueries.find_by(version: %w(foo bar baz).join("\n"))
+ end
+
+ recorder.count
+
+ expect(test_matcher.count_queries(recorder)).to eq({
+ 'SELECT "schema_migrations".* FROM "schema_migrations"' => {
+ %Q[WHERE "schema_migrations"."version" = 'foo\nbar\nbaz' LIMIT 1] => 2,
+ %Q[WHERE "schema_migrations"."version" = 'foo\nbiz\nbaz' LIMIT 1] => 1
+ }
+ })
+ end
+
+ it 'can aggregate queries' do
+ test_matcher = TestMatcher.new
+
+ recorder = ActiveRecord::QueryRecorder.new do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.count
+ TestQueries.create!(version: 'x')
+ TestQueries.first
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'x').update_all(version: 'y')
+ TestQueries.where(version: 'foobar').count
+ TestQueries.count
+ TestQueries.where(version: 'y').update_all(version: 'z')
+ TestQueries.where(version: 'z').delete_all
+ end
+
+ recorder.count
+
+ expect(test_matcher.count_queries(recorder)).to eq({
+ 'SELECT "schema_migrations".* FROM "schema_migrations"' => {
+ %q[WHERE "schema_migrations"."version" = 'foobar'] => 2,
+ %q[WHERE "schema_migrations"."version" = 'also foobar and baz'] => 1,
+ %q[ORDER BY "schema_migrations"."version" ASC LIMIT 1] => 1
+ },
+ 'SELECT COUNT(*) FROM "schema_migrations"' => {
+ "" => 2,
+ %q[WHERE "schema_migrations"."version" = 'foobar'] => 1
+ },
+ 'SAVEPOINT active_record_1' => { "" => 1 },
+ 'INSERT INTO "schema_migrations" ("version")' => {
+ %q[VALUES ('x') RETURNING "version"] => 1
+ },
+ 'RELEASE SAVEPOINT active_record_1' => { "" => 1 },
+ 'UPDATE "schema_migrations"' => {
+ %q[SET "version" = 'y' WHERE "schema_migrations"."version" = 'x'] => 1,
+ %q[SET "version" = 'z' WHERE "schema_migrations"."version" = 'y'] => 1
+ },
+ 'DELETE FROM "schema_migrations"' => {
+ %q[WHERE "schema_migrations"."version" = 'z'] => 1
+ }
+ })
+ end
+ end
+
+ it 'can count queries' do
+ test_matcher = TestMatcher.new
+ test_matcher.verify_count do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.first
+ TestQueries.count
+ end
+
+ expect(test_matcher.actual_count).to eq(4)
+ end
+
+ it 'can select specific queries' do
+ test_matcher = TestMatcher.new.for_query(/foobar/)
+ test_matcher.verify_count do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.first
+ TestQueries.count
+ end
+
+ expect(test_matcher.actual_count).to eq(2)
+ end
+
+ it 'can ignore specific queries' do
+ test_matcher = TestMatcher.new.ignoring(/foobar/)
+ test_matcher.verify_count do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.first
+ end
+
+ expect(test_matcher.actual_count).to eq(1)
+ end
+
+ it 'can perform inclusion and exclusion' do
+ test_matcher = TestMatcher.new.for_query(/foobar/).ignoring(/baz/)
+ test_matcher.verify_count do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.first
+ TestQueries.count
+ end
+
+ expect(test_matcher.actual_count).to eq(1)
+ end
+
it 'does not contain marginalia annotations' do
test_matcher = TestMatcher.new
test_matcher.verify_count do
diff --git a/spec/tasks/gettext_rake_spec.rb b/spec/tasks/gettext_rake_spec.rb
new file mode 100644
index 00000000000..a535ac92a75
--- /dev/null
+++ b/spec/tasks/gettext_rake_spec.rb
@@ -0,0 +1,177 @@
+# frozen_string_literal: true
+
+require "rake_helper"
+
+RSpec.describe 'gettext' do
+ let(:locale_path) { Rails.root.join('tmp/gettext_spec') }
+ let(:pot_file_path) { File.join(locale_path, 'gitlab.pot') }
+
+ before do
+ Rake.application.rake_require('tasks/gettext')
+
+ FileUtils.rm_r(locale_path) if Dir.exist?(locale_path)
+ FileUtils.mkdir_p(locale_path)
+
+ allow(Rails.root).to receive(:join).and_call_original
+ allow(Rails.root).to receive(:join).with('locale').and_return(locale_path)
+ end
+
+ after do
+ FileUtils.rm_r(locale_path) if Dir.exist?(locale_path)
+ end
+
+ describe ':compile' do
+ before do
+ allow(Rake::Task).to receive(:[]).and_call_original
+ end
+
+ it 'creates a pot file and invokes the \'gettext:po_to_json\' task' do
+ expect(Rake::Task).to receive(:[]).with('gettext:po_to_json').and_return(double(invoke: true))
+
+ expect { run_rake_task('gettext:compile') }
+ .to change { File.exist?(pot_file_path) }
+ .to be_truthy
+ end
+ end
+
+ describe ':regenerate' do
+ before do
+ # this task takes a *really* long time to complete, so stub it for the spec
+ allow(Rake::Task['gettext:find']).to receive(:invoke) { invoke_find.call }
+ end
+
+ context 'when the locale folder is not found' do
+ let(:invoke_find) { -> { true } }
+
+ before do
+ FileUtils.rm_r(locale_path) if Dir.exist?(locale_path)
+ end
+
+ it 'raises an error' do
+ expect { run_rake_task('gettext:regenerate') }
+ .to raise_error(/Cannot find '#{locale_path}' folder/)
+ end
+ end
+
+ context 'where there are existing /**/gitlab.po files' do
+ let(:locale_nz_path) { File.join(locale_path, 'en_NZ') }
+ let(:po_file_path) { File.join(locale_nz_path, 'gitlab.po') }
+
+ let(:invoke_find) { -> { File.write pot_file_path, 'pot file test updates' } }
+
+ before do
+ FileUtils.mkdir(locale_nz_path)
+ File.write(po_file_path, fixture_file('valid.po'))
+ end
+
+ it 'does not remove that locale' do
+ expect { run_rake_task('gettext:regenerate') }
+ .not_to change { Dir.exist?(locale_nz_path) }
+ end
+ end
+
+ context 'when there are locale folders without a gitlab.po file' do
+ let(:empty_locale_path) { File.join(locale_path, 'en_NZ') }
+
+ let(:invoke_find) { -> { File.write pot_file_path, 'pot file test updates' } }
+
+ before do
+ FileUtils.mkdir(empty_locale_path)
+ end
+
+ it 'removes those folders' do
+ expect { run_rake_task('gettext:regenerate') }
+ .to change { Dir.exist?(empty_locale_path) }
+ .to eq false
+ end
+ end
+
+ context 'when the gitlab.pot file cannot be generated' do
+ let(:invoke_find) { -> { true } }
+
+ it 'prints an error' do
+ expect { run_rake_task('gettext:regenerate') }
+ .to raise_error(/gitlab.pot file not generated/)
+ end
+ end
+
+ context 'when gettext:find changes the revision dates' do
+ let(:invoke_find) { -> { File.write pot_file_path, fixture_file('valid.po') } }
+
+ before do
+ File.write pot_file_path, fixture_file('valid.po')
+ end
+
+ it 'resets the changes' do
+ pot_file = File.read(pot_file_path)
+ expect(pot_file).to include('PO-Revision-Date: 2017-07-13 12:10-0500')
+ expect(pot_file).to include('PO-Creation-Date: 2016-07-13 12:11-0500')
+
+ run_rake_task('gettext:regenerate')
+
+ pot_file = File.read(pot_file_path)
+ expect(pot_file).not_to include('PO-Revision-Date: 2017-07-13 12:10-0500')
+ expect(pot_file).not_to include('PO-Creation-Date: 2016-07-13 12:11-0500')
+ end
+ end
+ end
+
+ describe ':lint' do
+ before do
+ # make sure we test on the fixture files, not the actual gitlab repo as
+ # this takes a long time
+ allow(Rails.root)
+ .to receive(:join)
+ .with('locale/*/gitlab.po')
+ .and_return(File.join(locale_path, '*/gitlab.po'))
+ end
+
+ context 'when all PO files are valid' do
+ before do
+ nz_locale_path = File.join(locale_path, 'en_NZ')
+ FileUtils.mkdir(nz_locale_path)
+
+ po_file_path = File.join(nz_locale_path, 'gitlab.po')
+ File.write(po_file_path, fixture_file('valid.po'))
+ File.write(pot_file_path, fixture_file('valid.po'))
+ end
+
+ it 'completes without error' do
+ expect { run_rake_task('gettext:lint') }
+ .not_to raise_error
+ end
+ end
+
+ context 'when there are invalid PO files' do
+ before do
+ nz_locale_path = File.join(locale_path, 'en_NZ')
+ FileUtils.mkdir(nz_locale_path)
+
+ po_file_path = File.join(nz_locale_path, 'gitlab.po')
+ File.write(po_file_path, fixture_file('invalid.po'))
+ File.write(pot_file_path, fixture_file('valid.po'))
+ end
+
+ it 'raises an error' do
+ expect { run_rake_task('gettext:lint') }
+ .to raise_error(/Not all PO-files are valid/)
+ end
+ end
+
+ context 'when the .pot file is invalid' do
+ before do
+ nz_locale_path = File.join(locale_path, 'en_NZ')
+ FileUtils.mkdir(nz_locale_path)
+
+ po_file_path = File.join(nz_locale_path, 'gitlab.po')
+ File.write(po_file_path, fixture_file('valid.po'))
+ File.write(pot_file_path, fixture_file('invalid.po'))
+ end
+
+ it 'raises an error' do
+ expect { run_rake_task('gettext:lint') }
+ .to raise_error(/Not all PO-files are valid/)
+ end
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
index 046e066a45f..edfdb44022b 100644
--- a/spec/tasks/gitlab/db_rake_spec.rb
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -129,7 +129,7 @@ RSpec.describe 'gitlab:db namespace rake task' do
let(:output) { StringIO.new }
before do
- allow(File).to receive(:read).with(structure_file).and_return(input)
+ stub_file_read(structure_file, content: input)
allow(File).to receive(:open).with(structure_file, any_args).and_yield(output)
end
@@ -235,8 +235,8 @@ RSpec.describe 'gitlab:db namespace rake task' do
let(:indexes) { double('indexes') }
context 'when no index_name is given' do
- it 'rebuilds a random number of large indexes' do
- expect(Gitlab::Database::Reindexing).to receive_message_chain('candidate_indexes.random_few').and_return(indexes)
+ it 'uses all candidate indexes' do
+ expect(Gitlab::Database::Reindexing).to receive(:candidate_indexes).and_return(indexes)
expect(Gitlab::Database::Reindexing).to receive(:perform).with(indexes)
run_rake_task('gitlab:db:reindex')
@@ -246,17 +246,53 @@ RSpec.describe 'gitlab:db namespace rake task' do
context 'with index name given' do
let(:index) { double('index') }
+ before do
+ allow(Gitlab::Database::Reindexing).to receive(:candidate_indexes).and_return(indexes)
+ end
+
it 'calls the index rebuilder with the proper arguments' do
- expect(Gitlab::Database::PostgresIndex).to receive(:by_identifier).with('public.foo_idx').and_return(index)
+ allow(indexes).to receive(:where).with(identifier: 'public.foo_idx').and_return([index])
expect(Gitlab::Database::Reindexing).to receive(:perform).with([index])
run_rake_task('gitlab:db:reindex', '[public.foo_idx]')
end
it 'raises an error if the index does not exist' do
- expect(Gitlab::Database::PostgresIndex).to receive(:by_identifier).with('public.absent_index').and_raise(ActiveRecord::RecordNotFound)
+ allow(indexes).to receive(:where).with(identifier: 'public.absent_index').and_return([])
+
+ expect { run_rake_task('gitlab:db:reindex', '[public.absent_index]') }.to raise_error(/Index not found/)
+ end
+
+ it 'raises an error if the index is not fully qualified with a schema' do
+ expect { run_rake_task('gitlab:db:reindex', '[foo_idx]') }.to raise_error(/Index name is not fully qualified/)
+ end
+ end
+ end
+
+ describe 'active' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:task) { 'gitlab:db:active' }
+ let(:self_monitoring) { double('self_monitoring') }
- expect { run_rake_task('gitlab:db:reindex', '[public.absent_index]') }.to raise_error(ActiveRecord::RecordNotFound)
+ where(:needs_migration, :self_monitoring_project, :project_count, :exit_status, :exit_code) do
+ true | nil | nil | 1 | false
+ false | :self_monitoring | 1 | 1 | false
+ false | nil | 0 | 1 | false
+ false | :self_monitoring | 2 | 0 | true
+ end
+
+ with_them do
+ it 'exits 0 or 1 depending on user modifications to the database' do
+ allow_any_instance_of(ActiveRecord::MigrationContext).to receive(:needs_migration?).and_return(needs_migration)
+ allow_any_instance_of(ApplicationSetting).to receive(:self_monitoring_project).and_return(self_monitoring_project)
+ allow(Project).to receive(:count).and_return(project_count)
+
+ expect { run_rake_task(task) }.to raise_error do |error|
+ expect(error).to be_a(SystemExit)
+ expect(error.status).to eq(exit_status)
+ expect(error.success?).to be(exit_code)
+ end
end
end
end
diff --git a/spec/tasks/gitlab/ldap_rake_spec.rb b/spec/tasks/gitlab/ldap_rake_spec.rb
index 275fcb98dd0..5286cd98944 100644
--- a/spec/tasks/gitlab/ldap_rake_spec.rb
+++ b/spec/tasks/gitlab/ldap_rake_spec.rb
@@ -13,3 +13,112 @@ RSpec.describe 'gitlab:ldap:rename_provider rake task' do
run_rake_task('gitlab:ldap:rename_provider', 'ldapmain', 'ldapfoo')
end
end
+
+RSpec.describe 'gitlab:ldap:secret rake tasks' do
+ let(:ldap_secret_file) { 'tmp/tests/ldapenc/ldap_secret.yaml.enc' }
+
+ before do
+ Rake.application.rake_require 'tasks/gitlab/ldap'
+ stub_env('EDITOR', 'cat')
+ stub_warn_user_is_not_gitlab
+ FileUtils.mkdir_p('tmp/tests/ldapenc/')
+ allow(Gitlab.config.ldap).to receive(:secret_file).and_return(ldap_secret_file)
+ allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
+ end
+
+ after do
+ FileUtils.rm_rf(Rails.root.join('tmp/tests/ldapenc'))
+ end
+
+ describe ':show' do
+ it 'displays error when file does not exist' do
+ expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/File .* does not exist. Use `gitlab-rake gitlab:ldap:secret:edit` to change that./).to_stdout
+ end
+
+ it 'displays error when key does not exist' do
+ Settings.encrypted(ldap_secret_file).write('somevalue')
+ allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
+ expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stdout
+ end
+
+ it 'displays error when key is changed' do
+ Settings.encrypted(ldap_secret_file).write('somevalue')
+ allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
+ expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stdout
+ end
+
+ it 'outputs the unencrypted content when present' do
+ encrypted = Settings.encrypted(ldap_secret_file)
+ encrypted.write('somevalue')
+ expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/somevalue/).to_stdout
+ end
+ end
+
+ describe 'edit' do
+ it 'creates encrypted file' do
+ expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/File encrypted and saved./).to_stdout
+ expect(File.exist?(ldap_secret_file)).to be true
+ value = Settings.encrypted(ldap_secret_file)
+ expect(value.read).to match(/password: '123'/)
+ end
+
+ it 'displays error when key does not exist' do
+ allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
+ expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stdout
+ end
+
+ it 'displays error when key is changed' do
+ Settings.encrypted(ldap_secret_file).write('somevalue')
+ allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
+ expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stdout
+ end
+
+ it 'displays error when write directory does not exist' do
+ FileUtils.rm_rf(Rails.root.join('tmp/tests/ldapenc'))
+ expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Directory .* does not exist./).to_stdout
+ end
+
+ it 'shows a warning when content is invalid' do
+ Settings.encrypted(ldap_secret_file).write('somevalue')
+ expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/WARNING: Content was not a valid LDAP secret yml file/).to_stdout
+ value = Settings.encrypted(ldap_secret_file)
+ expect(value.read).to match(/somevalue/)
+ end
+
+ it 'displays error when $EDITOR is not set' do
+ stub_env('EDITOR', nil)
+ expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/No \$EDITOR specified to open file. Please provide one when running the command/).to_stdout
+ end
+ end
+
+ describe 'write' do
+ before do
+ allow(STDIN).to receive(:tty?).and_return(false)
+ allow(STDIN).to receive(:read).and_return('testvalue')
+ end
+
+ it 'creates encrypted file from stdin' do
+ expect { run_rake_task('gitlab:ldap:secret:write') }.to output(/File encrypted and saved./).to_stdout
+ expect(File.exist?(ldap_secret_file)).to be true
+ value = Settings.encrypted(ldap_secret_file)
+ expect(value.read).to match(/testvalue/)
+ end
+
+ it 'displays error when key does not exist' do
+ allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
+ expect { run_rake_task('gitlab:ldap:secret:write') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stdout
+ end
+
+ it 'displays error when write directory does not exist' do
+ FileUtils.rm_rf('tmp/tests/ldapenc/')
+ expect { run_rake_task('gitlab:ldap:secret:write') }.to output(/Directory .* does not exist./).to_stdout
+ end
+
+ it 'shows a warning when content is invalid' do
+ Settings.encrypted(ldap_secret_file).write('somevalue')
+ expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/WARNING: Content was not a valid LDAP secret yml file/).to_stdout
+ value = Settings.encrypted(ldap_secret_file)
+ expect(value.read).to match(/somevalue/)
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/packages/events_rake_spec.rb b/spec/tasks/gitlab/packages/events_rake_spec.rb
index ac28f9b5fc2..a485dc2ce58 100644
--- a/spec/tasks/gitlab/packages/events_rake_spec.rb
+++ b/spec/tasks/gitlab/packages/events_rake_spec.rb
@@ -7,32 +7,49 @@ RSpec.describe 'gitlab:packages:events namespace rake task' do
Rake.application.rake_require 'tasks/gitlab/packages/events'
end
- describe 'generate' do
- subject do
- file = double('file')
- yml_file = nil
+ subject do
+ file = double('file')
+ yml_file = nil
- allow(file).to receive(:<<) { |contents| yml_file = contents }
- allow(File).to receive(:open).and_yield(file)
+ allow(file).to receive(:<<) { |contents| yml_file = contents }
+ allow(File).to receive(:open).and_yield(file)
- run_rake_task('gitlab:packages:events:generate')
+ run_rake_task("gitlab:packages:events:#{task}")
- YAML.safe_load(yml_file)
- end
+ YAML.safe_load(yml_file)
+ end
+
+ describe 'generate_unique' do
+ let(:task) { 'generate_unique' }
it 'excludes guest events' do
expect(subject.find { |event| event['name'].include?("guest") }).to be_nil
end
- ::Packages::Event::EVENT_SCOPES.keys.each do |event_scope|
- it "includes includes `#{event_scope}` scope" do
+ Packages::Event::EVENT_SCOPES.keys.each do |event_scope|
+ it "includes `#{event_scope}` scope" do
expect(subject.find { |event| event['name'].include?(event_scope) }).not_to be_nil
end
end
it 'excludes some event types' do
- expect(subject.find { |event| event['name'].include?("search_package") }).to be_nil
- expect(subject.find { |event| event['name'].include?("list_package") }).to be_nil
+ expect(subject.grep(/search_package/)).to be_empty
+ expect(subject.grep(/list_package/)).to be_empty
+ end
+ end
+
+ describe 'generate_guest' do
+ let(:task) { 'generate_guest' }
+
+ Packages::Event::EVENT_SCOPES.keys.each do |event_scope|
+ it "includes `#{event_scope}` scope" do
+ expect(subject.find { |event| event.include?(event_scope) }).not_to be_nil
+ end
+ end
+
+ it 'excludes some event types' do
+ expect(subject.find { |event| event.include?("search_package") }).to be_nil
+ expect(subject.find { |event| event.include?("list_package") }).to be_nil
end
end
end
diff --git a/spec/tasks/gitlab/user_management_rake_spec.rb b/spec/tasks/gitlab/user_management_rake_spec.rb
new file mode 100644
index 00000000000..958055780d0
--- /dev/null
+++ b/spec/tasks/gitlab/user_management_rake_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+RSpec.describe 'gitlab:user_management tasks' do
+ before do
+ Rake.application.rake_require 'tasks/gitlab/user_management'
+ end
+
+ describe 'disable_project_and_group_creation' do
+ let(:group) { create(:group) }
+
+ subject(:run_rake) { run_rake_task('gitlab:user_management:disable_project_and_group_creation', group.id) }
+
+ it 'returns output info' do
+ expect { run_rake }.to output(/.*Done.*/).to_stdout
+ end
+
+ context 'with users' do
+ let(:user_1) { create(:user, projects_limit: 10, can_create_group: true) }
+ let(:user_2) { create(:user, projects_limit: 10, can_create_group: true) }
+ let(:user_other) { create(:user, projects_limit: 10, can_create_group: true) }
+
+ shared_examples 'updates proper users' do
+ it 'updates members' do
+ run_rake
+
+ expect(user_1.reload.projects_limit).to eq(0)
+ expect(user_1.can_create_group).to eq(false)
+ expect(user_2.reload.projects_limit).to eq(0)
+ expect(user_2.can_create_group).to eq(false)
+ end
+
+ it 'does not update other users' do
+ run_rake
+
+ expect(user_other.reload.projects_limit).to eq(10)
+ expect(user_other.reload.can_create_group).to eq(true)
+ end
+ end
+
+ context 'in the group' do
+ let(:other_group) { create(:group) }
+
+ before do
+ group.add_developer(user_1)
+ group.add_developer(user_2)
+ other_group.add_developer(user_other)
+ end
+
+ it_behaves_like 'updates proper users'
+ end
+
+ context 'in the descendant groups' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:sub_subgroup) { create(:group, parent: subgroup) }
+ let(:other_group) { create(:group) }
+
+ before do
+ subgroup.add_developer(user_1)
+ sub_subgroup.add_developer(user_2)
+ other_group.add_developer(user_other)
+ end
+
+ it_behaves_like 'updates proper users'
+ end
+
+ context 'in the children projects' do
+ let(:project_1) { create(:project, namespace: group) }
+ let(:project_2) { create(:project, namespace: group) }
+ let(:other_project) { create(:project) }
+
+ before do
+ project_1.add_developer(user_1)
+ project_2.add_developer(user_2)
+ other_project.add_developer(user_other)
+ end
+
+ it_behaves_like 'updates proper users'
+ end
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb
index 2efbf0febac..0757f6ca015 100644
--- a/spec/tasks/gitlab/workhorse_rake_spec.rb
+++ b/spec/tasks/gitlab/workhorse_rake_spec.rb
@@ -9,8 +9,12 @@ RSpec.describe 'gitlab:workhorse namespace rake task' do
describe 'install' do
let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' }
- let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s }
- let(:version) { File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp }
+ let(:clone_path) { Dir.mktmpdir('gitlab:workhorse:install-rake-test') }
+ let(:workhorse_source) { Rails.root.join('workhorse').to_s }
+
+ after do
+ FileUtils.rm_rf(clone_path)
+ end
context 'no dir given' do
it 'aborts and display a help message' do
@@ -20,7 +24,7 @@ RSpec.describe 'gitlab:workhorse namespace rake task' do
end
end
- context 'when an underlying Git command fail' do
+ context 'when an underlying Git command fails' do
it 'aborts and display a help message' do
expect(main_object)
.to receive(:checkout_or_clone_version).and_raise 'Git error'
@@ -29,52 +33,26 @@ RSpec.describe 'gitlab:workhorse namespace rake task' do
end
end
- describe 'checkout or clone' do
- before do
- expect(Dir).to receive(:chdir).with(clone_path)
- end
-
- it 'calls checkout_or_clone_version with the right arguments' do
- expect(main_object)
- .to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path, clone_opts: %w[--depth 1])
-
- run_rake_task('gitlab:workhorse:install', clone_path)
- end
- end
-
- describe 'gmake/make' do
- before do
- FileUtils.mkdir_p(clone_path)
- expect(Dir).to receive(:chdir).with(clone_path).and_call_original
- end
-
- context 'gmake is available' do
- before do
- expect(main_object).to receive(:checkout_or_clone_version)
- allow(Object).to receive(:run_command!).with(['gmake']).and_return(true)
+ it 'clones the origin and creates a gitlab-workhorse binary' do
+ FileUtils.rm_rf(clone_path)
+
+ Dir.mktmpdir('fake-workhorse-origin') do |workhorse_origin|
+ [
+ %W[git init -q #{workhorse_origin}],
+ %W[git -C #{workhorse_origin} checkout -q -b workhorse-move-notice],
+ %W[touch #{workhorse_origin}/proof-that-repo-got-cloned],
+ %W[git -C #{workhorse_origin} add .],
+ %W[git -C #{workhorse_origin} commit -q -m init],
+ %W[git -C #{workhorse_origin} checkout -q -b master]
+ ].each do |cmd|
+ raise "#{cmd.join(' ')} failed" unless system(*cmd)
end
- it 'calls gmake in the gitlab-workhorse directory' do
- expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
- expect(main_object).to receive(:run_command!).with(['gmake']).and_return(true)
-
- run_rake_task('gitlab:workhorse:install', clone_path)
- end
+ run_rake_task('gitlab:workhorse:install', clone_path, File.join(workhorse_origin, '.git'))
end
- context 'gmake is not available' do
- before do
- expect(main_object).to receive(:checkout_or_clone_version)
- allow(main_object).to receive(:run_command!).with(['make']).and_return(true)
- end
-
- it 'calls make in the gitlab-workhorse directory' do
- expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42])
- expect(main_object).to receive(:run_command!).with(['make']).and_return(true)
-
- run_rake_task('gitlab:workhorse:install', clone_path)
- end
- end
+ expect(File.exist?(File.join(clone_path, 'proof-that-repo-got-cloned'))).to be true
+ expect(File.executable?(File.join(clone_path, 'gitlab-workhorse'))).to be true
end
end
end
diff --git a/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb b/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
new file mode 100644
index 00000000000..4b44d991d89
--- /dev/null
+++ b/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require_relative '../../../../tooling/lib/tooling/parallel_rspec_runner'
+
+RSpec.describe Tooling::ParallelRSpecRunner do # rubocop:disable RSpec/FilePath
+ describe '#run' do
+ let(:allocator) { instance_double(Knapsack::Allocator) }
+ let(:rspec_args) { '--seed 123' }
+ let(:filter_tests_file) { 'tests.txt' }
+ let(:node_tests) { %w[01_spec.rb 03_spec.rb 05_spec.rb] }
+ let(:filter_tests) { '01_spec.rb 02_spec.rb 03_spec.rb' }
+ let(:test_dir) { 'spec' }
+
+ before do
+ allow(Knapsack.logger).to receive(:info)
+ allow(allocator).to receive(:node_tests).and_return(node_tests)
+ allow(allocator).to receive(:test_dir).and_return(test_dir)
+ allow(File).to receive(:exist?).with(filter_tests_file).and_return(true)
+ allow(File).to receive(:read).and_call_original
+ allow(File).to receive(:read).with(filter_tests_file).and_return(filter_tests)
+ allow(subject).to receive(:exec)
+ end
+
+ subject { described_class.new(allocator: allocator, filter_tests_file: filter_tests_file, rspec_args: rspec_args) }
+
+ shared_examples 'runs node tests' do
+ it 'runs rspec with tests allocated for this node' do
+ expect_command(%w[bundle exec rspec --seed 123 --default-path spec -- 01_spec.rb 03_spec.rb 05_spec.rb])
+
+ subject.run
+ end
+ end
+
+ context 'given filter tests' do
+ it 'reads filter tests file for list of tests' do
+ expect(File).to receive(:read).with(filter_tests_file)
+
+ subject.run
+ end
+
+ it 'runs rspec filter tests that are allocated for this node' do
+ expect_command(%w[bundle exec rspec --seed 123 --default-path spec -- 01_spec.rb 03_spec.rb])
+
+ subject.run
+ end
+
+ context 'when there is no intersect between allocated tests and filtered tests' do
+ let(:filter_tests) { '99_spec.rb' }
+
+ it 'does not run rspec' do
+ expect(subject).not_to receive(:exec)
+
+ subject.run
+ end
+ end
+ end
+
+ context 'with empty filter tests file' do
+ let(:filter_tests) { '' }
+
+ it_behaves_like 'runs node tests'
+ end
+
+ context 'without filter_tests_file option' do
+ let(:filter_tests_file) { nil }
+
+ it_behaves_like 'runs node tests'
+ end
+
+ context 'if filter_tests_file does not exist' do
+ before do
+ allow(File).to receive(:exist?).with(filter_tests_file).and_return(false)
+ end
+
+ it_behaves_like 'runs node tests'
+ end
+
+ context 'without rspec args' do
+ let(:rspec_args) { nil }
+
+ it 'runs rspec with without extra arguments' do
+ expect_command(%w[bundle exec rspec --default-path spec -- 01_spec.rb 03_spec.rb])
+
+ subject.run
+ end
+ end
+
+ def expect_command(cmd)
+ expect(subject).to receive(:exec).with(*cmd)
+ end
+ end
+end
diff --git a/spec/tooling/lib/tooling/test_map_generator_spec.rb b/spec/tooling/lib/tooling/test_map_generator_spec.rb
index 7f3b2807162..eb49b1db20e 100644
--- a/spec/tooling/lib/tooling/test_map_generator_spec.rb
+++ b/spec/tooling/lib/tooling/test_map_generator_spec.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
require_relative '../../../../tooling/lib/tooling/test_map_generator'
+require_relative '../../../support/helpers/file_read_helpers'
RSpec.describe Tooling::TestMapGenerator do
+ include FileReadHelpers
+
subject { described_class.new }
describe '#parse' do
@@ -39,21 +42,21 @@ RSpec.describe Tooling::TestMapGenerator do
let(:pathname) { instance_double(Pathname) }
before do
- allow(File).to receive(:read).with('yaml1.yml').and_return(yaml1)
- allow(File).to receive(:read).with('yaml2.yml').and_return(yaml2)
+ stub_file_read('yaml1.yml', content: yaml1)
+ stub_file_read('yaml2.yml', content: yaml2)
end
context 'with single yaml' do
let(:expected_mapping) do
{
'lib/gitlab/current_settings.rb' => [
- './spec/factories_spec.rb'
+ 'spec/factories_spec.rb'
],
'lib/feature.rb' => [
- './spec/factories_spec.rb'
+ 'spec/factories_spec.rb'
],
'lib/gitlab/marginalia.rb' => [
- './spec/factories_spec.rb'
+ 'spec/factories_spec.rb'
]
}
end
@@ -77,16 +80,16 @@ RSpec.describe Tooling::TestMapGenerator do
let(:expected_mapping) do
{
'lib/gitlab/current_settings.rb' => [
- './spec/factories_spec.rb',
- './spec/models/project_spec.rb'
+ 'spec/factories_spec.rb',
+ 'spec/models/project_spec.rb'
],
'lib/feature.rb' => [
- './spec/factories_spec.rb',
- './spec/models/project_spec.rb'
+ 'spec/factories_spec.rb',
+ 'spec/models/project_spec.rb'
],
'lib/gitlab/marginalia.rb' => [
- './spec/factories_spec.rb',
- './spec/models/project_spec.rb'
+ 'spec/factories_spec.rb',
+ 'spec/models/project_spec.rb'
]
}
end
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index ba8d0ccbd02..a1d8695a8c9 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -515,7 +515,7 @@ RSpec.describe ObjectStorage do
end
context 'uses AWS' do
- let(:storage_url) { "https://uploads.s3-eu-central-1.amazonaws.com/" }
+ let(:storage_url) { "https://uploads.s3.eu-central-1.amazonaws.com/" }
let(:credentials) do
{
provider: "AWS",
diff --git a/spec/uploaders/terraform/state_uploader_spec.rb b/spec/uploaders/terraform/state_uploader_spec.rb
index dadfdf6e93f..bd8e7fbc016 100644
--- a/spec/uploaders/terraform/state_uploader_spec.rb
+++ b/spec/uploaders/terraform/state_uploader_spec.rb
@@ -3,23 +3,45 @@
require 'spec_helper'
RSpec.describe Terraform::StateUploader do
- subject { terraform_state.file }
+ subject { state_version.file }
- let(:terraform_state) { create(:terraform_state, :with_file) }
+ let(:state_version) { create(:terraform_state_version) }
before do
stub_terraform_state_object_storage
end
describe '#filename' do
- it 'contains the UUID of the terraform state record' do
- expect(subject.filename).to include(terraform_state.uuid)
+ it 'contains the version of the terraform state record' do
+ expect(subject.filename).to eq("#{state_version.version}.tfstate")
+ end
+
+ context 'legacy state with versioning disabled' do
+ let(:state) { create(:terraform_state, versioning_enabled: false) }
+ let(:state_version) { create(:terraform_state_version, terraform_state: state) }
+
+ it 'contains the UUID of the terraform state record' do
+ expect(subject.filename).to eq("#{state_version.uuid}.tfstate")
+ end
end
end
describe '#store_dir' do
- it 'contains the ID of the project' do
- expect(subject.store_dir).to include(terraform_state.project_id.to_s)
+ it 'hashes the project ID and UUID' do
+ expect(Gitlab::HashedPath).to receive(:new)
+ .with(state_version.uuid, root_hash: state_version.project_id)
+ .and_return(:store_dir)
+
+ expect(subject.store_dir).to eq(:store_dir)
+ end
+
+ context 'legacy state with versioning disabled' do
+ let(:state) { create(:terraform_state, versioning_enabled: false) }
+ let(:state_version) { create(:terraform_state_version, terraform_state: state) }
+
+ it 'contains the ID of the project' do
+ expect(subject.store_dir).to include(state_version.project_id.to_s)
+ end
end
end
@@ -27,7 +49,7 @@ RSpec.describe Terraform::StateUploader do
it 'creates a digest with a secret key and the project id' do
expect(OpenSSL::HMAC)
.to receive(:digest)
- .with('SHA256', Gitlab::Application.secrets.db_key_base, terraform_state.project_id.to_s)
+ .with('SHA256', Gitlab::Application.secrets.db_key_base, state_version.project_id.to_s)
.and_return('digest')
expect(subject.key).to eq('digest')
diff --git a/spec/uploaders/terraform/versioned_state_uploader_spec.rb b/spec/uploaders/terraform/versioned_state_uploader_spec.rb
deleted file mode 100644
index eeb54cb61c7..00000000000
--- a/spec/uploaders/terraform/versioned_state_uploader_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Terraform::VersionedStateUploader do
- subject { model.file }
-
- let(:model) { create(:terraform_state_version, :with_file) }
-
- before do
- stub_terraform_state_object_storage
- end
-
- describe '#filename' do
- it 'contains the version of the terraform state record' do
- expect(subject.filename).to eq("#{model.version}.tfstate")
- end
-
- context 'legacy state with versioning disabled' do
- let(:state) { create(:legacy_terraform_state) }
- let(:model) { create(:terraform_state_version, terraform_state: state) }
-
- it 'contains the UUID of the terraform state record' do
- expect(subject.filename).to eq("#{model.uuid}.tfstate")
- end
- end
- end
-
- describe '#store_dir' do
- it 'hashes the project ID and UUID' do
- expect(Gitlab::HashedPath).to receive(:new)
- .with(model.uuid, root_hash: model.project_id)
- .and_return(:store_dir)
-
- expect(subject.store_dir).to eq(:store_dir)
- end
-
- context 'legacy state with versioning disabled' do
- let(:state) { create(:legacy_terraform_state) }
- let(:model) { create(:terraform_state_version, terraform_state: state) }
-
- it 'contains the ID of the project' do
- expect(subject.store_dir).to include(model.project_id.to_s)
- end
- end
- end
-end
diff --git a/spec/validators/json_schema_validator_spec.rb b/spec/validators/json_schema_validator_spec.rb
index 83eb0e2f3dd..1e9420c5422 100644
--- a/spec/validators/json_schema_validator_spec.rb
+++ b/spec/validators/json_schema_validator_spec.rb
@@ -29,6 +29,36 @@ RSpec.describe JsonSchemaValidator do
expect(build_report_result.errors.full_messages).to eq(["Data must be a valid json schema"])
end
end
+
+ context 'when draft is > 4' do
+ let(:validator) { described_class.new(attributes: [:data], filename: "build_report_result_data", draft: 6) }
+
+ it 'uses JSONSchemer to perform validations' do
+ expect(JSONSchemer).to receive(:schema).with(Pathname.new(Rails.root.join('app', 'validators', 'json_schemas', 'build_report_result_data.json').to_s)).and_call_original
+
+ subject
+ end
+ end
+
+ context 'when draft is <= 4' do
+ let(:validator) { described_class.new(attributes: [:data], filename: "build_report_result_data", draft: 4) }
+
+ it 'uses JSON::Validator to perform validations' do
+ expect(JSON::Validator).to receive(:validate).with(Rails.root.join('app', 'validators', 'json_schemas', 'build_report_result_data.json').to_s, build_report_result.data)
+
+ subject
+ end
+ end
+
+ context 'when draft value is not provided' do
+ let(:validator) { described_class.new(attributes: [:data], filename: "build_report_result_data") }
+
+ it 'uses JSON::Validator to perform validations' do
+ expect(JSON::Validator).to receive(:validate).with(Rails.root.join('app', 'validators', 'json_schemas', 'build_report_result_data.json').to_s, build_report_result.data)
+
+ subject
+ end
+ end
end
context 'when filename is not set' do
diff --git a/spec/views/admin/application_settings/general.html.haml_spec.rb b/spec/views/admin/application_settings/general.html.haml_spec.rb
index 5343847d755..434ca8bf0e7 100644
--- a/spec/views/admin/application_settings/general.html.haml_spec.rb
+++ b/spec/views/admin/application_settings/general.html.haml_spec.rb
@@ -33,32 +33,4 @@ RSpec.describe 'admin/application_settings/general.html.haml' do
end
end
end
-
- describe 'Maintenance mode' do
- let(:maintenance_mode_flag) { true }
-
- before do
- assign(:application_setting, app_settings)
- stub_feature_flags(maintenance_mode: maintenance_mode_flag)
- allow(view).to receive(:current_user).and_return(user)
- end
-
- context 'when maintenance_mode feature is enabled' do
- it 'show the Maintenance mode section' do
- render
-
- expect(rendered).to have_css('#js-maintenance-mode-toggle')
- end
- end
-
- context 'when maintenance_mode feature is disabled' do
- let(:maintenance_mode_flag) { false }
-
- it 'hide the Maintenance mode section' do
- render
-
- expect(rendered).not_to have_css('#js-maintenance-mode-toggle')
- end
- end
- end
end
diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb
index e9223c84674..5494b908705 100644
--- a/spec/views/admin/dashboard/index.html.haml_spec.rb
+++ b/spec/views/admin/dashboard/index.html.haml_spec.rb
@@ -17,7 +17,6 @@ RSpec.describe 'admin/dashboard/index.html.haml' do
allow(view).to receive(:admin?).and_return(true)
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
- allow(view).to receive(:show_license_breakdown?).and_return(false)
end
it "shows version of GitLab Workhorse" do
diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb
index 25fcbeb61df..d8748873f64 100644
--- a/spec/views/layouts/_head.html.haml_spec.rb
+++ b/spec/views/layouts/_head.html.haml_spec.rb
@@ -86,21 +86,21 @@ RSpec.describe 'layouts/_head' do
end
end
- context 'when a Piwik config is set' do
- let(:piwik_host) { 'piwik.example.com' }
+ context 'when a Matomo config is set' do
+ let(:matomo_host) { 'matomo.example.com' }
before do
stub_config(extra: {
- piwik_url: piwik_host,
- piwik_site_id: 12345
+ matomo_url: matomo_host,
+ matomo_site_id: 12345
})
end
- it 'add a Piwik Javascript' do
+ it 'add a Matomo Javascript' do
render
- expect(rendered).to match(/<script.*>.*var u="\/\/#{piwik_host}\/".*<\/script>/m)
- expect(rendered).to match(%r(<noscript>.*<img src="//#{piwik_host}/piwik.php.*</noscript>))
+ expect(rendered).to match(/<script.*>.*var u="\/\/#{matomo_host}\/".*<\/script>/m)
+ expect(rendered).to match(%r(<noscript>.*<img src="//#{matomo_host}/matomo.php.*</noscript>))
end
end
diff --git a/spec/views/projects/artifacts/_artifact.html.haml_spec.rb b/spec/views/projects/artifacts/_artifact.html.haml_spec.rb
index b3bf54e143a..5d930d6b0f2 100644
--- a/spec/views/projects/artifacts/_artifact.html.haml_spec.rb
+++ b/spec/views/projects/artifacts/_artifact.html.haml_spec.rb
@@ -16,10 +16,21 @@ RSpec.describe "projects/artifacts/_artifact.html.haml" do
context 'with admin' do
let(:user) { build(:admin) }
- it 'has a delete button' do
- render_partial
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it 'has a delete button' do
+ render_partial
- expect(rendered).to have_link('Delete artifacts', href: project_artifact_path(project, project.job_artifacts.first))
+ expect(rendered).to have_link('Delete artifacts', href: project_artifact_path(project, project.job_artifacts.first))
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it 'has no delete button' do
+ project.add_reporter(user)
+ render_partial
+
+ expect(rendered).not_to have_link('Delete artifacts')
+ end
end
end
diff --git a/spec/views/projects/commits/_commit.html.haml_spec.rb b/spec/views/projects/commits/_commit.html.haml_spec.rb
index 898d3baae19..abbb3a168c3 100644
--- a/spec/views/projects/commits/_commit.html.haml_spec.rb
+++ b/spec/views/projects/commits/_commit.html.haml_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe 'projects/commits/_commit.html.haml' do
}
within '.gpg-status-box' do
- expect(page).not_to have_css('i.fa.fa-spinner.fa-spin')
+ expect(page).not_to have_css('.gl-spinner')
end
end
end
diff --git a/spec/views/projects/empty.html.haml_spec.rb b/spec/views/projects/empty.html.haml_spec.rb
new file mode 100644
index 00000000000..de83722160e
--- /dev/null
+++ b/spec/views/projects/empty.html.haml_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'projects/empty' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { ProjectPresenter.new(create(:project, :empty_repo), current_user: user) }
+
+ before do
+ allow(view).to receive(:experiment_enabled?).and_return(true)
+ allow(view).to receive(:current_user).and_return(user)
+ assign(:project, project)
+ end
+
+ context 'when user can push code on the project' do
+ before do
+ allow(view).to receive(:can?).with(user, :push_code, project).and_return(true)
+ end
+
+ it 'displays "git clone" instructions' do
+ render
+
+ expect(rendered).to have_content("git clone")
+ end
+ end
+
+ context 'when user can not push code on the project' do
+ before do
+ allow(view).to receive(:can?).with(user, :push_code, project).and_return(false)
+ end
+
+ it 'does not display "git clone" instructions' do
+ render
+
+ expect(rendered).not_to have_content("git clone")
+ end
+ end
+
+ describe 'invite_members_empty_project_version_a experiment' do
+ let(:can_import_members) { true }
+
+ before do
+ allow(view).to receive(:can_import_members?).and_return(can_import_members)
+ end
+
+ shared_examples_for 'no invite member info' do
+ it 'does not show invite member info' do
+ render
+
+ expect(rendered).not_to have_content('Invite your team')
+ end
+ end
+
+ context 'when experiment is enabled' do
+ it 'shows invite members info', :aggregate_failures do
+ render
+
+ expect(rendered).to have_selector('[data-track-event=render]')
+ expect(rendered).to have_selector('[data-track-label=invite_members_empty_project]', count: 2)
+ expect(rendered).to have_content('Invite your team')
+ expect(rendered).to have_content('Add members to this project and start collaborating with your team.')
+ expect(rendered).to have_link('Invite members', href: project_project_members_path(project, sort: :access_level_desc))
+ expect(rendered).to have_selector('[data-track-event=click_button]')
+ end
+
+ context 'when user does not have permissions to invite members' do
+ let(:can_import_members) { false }
+
+ it_behaves_like 'no invite member info'
+ end
+ end
+
+ context 'when experiment is not enabled' do
+ before do
+ allow(view).to receive(:experiment_enabled?)
+ .with(:invite_members_empty_project_version_a).and_return(false)
+ end
+
+ it_behaves_like 'no invite member info'
+ end
+ end
+end
diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
index 9b4f2774c5b..db41c9b5374 100644
--- a/spec/views/projects/merge_requests/show.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -3,30 +3,45 @@
require 'spec_helper'
RSpec.describe 'projects/merge_requests/show.html.haml' do
+ include Spec::Support::Helpers::Features::MergeRequestHelpers
+
before do
allow(view).to receive(:experiment_enabled?).and_return(false)
end
- include_context 'merge request show action'
-
- describe 'merge request assignee sidebar' do
- context 'when assignee is allowed to merge' do
- it 'does not show a warning icon' do
- closed_merge_request.update!(assignee_id: user.id)
- project.add_maintainer(user)
- assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request))
+ context 'when the merge request is open' do
+ include_context 'open merge request show action'
- render
+ it 'shows the "Mark as draft" button' do
+ render
- expect(rendered).not_to have_css('.merge-icon')
- end
+ expect(rendered).to have_css('a', visible: true, text: 'Mark as draft')
+ expect(rendered).to have_css('a', visible: false, text: 'Reopen')
+ expect(rendered).to have_css('a', visible: true, text: 'Close')
end
end
context 'when the merge request is closed' do
+ include_context 'closed merge request show action'
+
+ describe 'merge request assignee sidebar' do
+ context 'when assignee is allowed to merge' do
+ it 'does not show a warning icon' do
+ closed_merge_request.update!(assignee_id: user.id)
+ project.add_maintainer(user)
+ assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request))
+
+ render
+
+ expect(rendered).not_to have_css('.merge-icon')
+ end
+ end
+ end
+
it 'shows the "Reopen" button' do
render
+ expect(rendered).not_to have_css('a', visible: true, text: 'Mark as draft')
expect(rendered).to have_css('a', visible: true, text: 'Reopen')
expect(rendered).to have_css('a', visible: false, text: 'Close')
end
@@ -34,7 +49,7 @@ RSpec.describe 'projects/merge_requests/show.html.haml' do
it 'does not show the "Reopen" button when the source project does not exist' do
unlink_project.execute
closed_merge_request.reload
- preload_view_requirements
+ preload_view_requirements(closed_merge_request, note)
render
diff --git a/spec/views/search/_filter.html.haml_spec.rb b/spec/views/search/_filter.html.haml_spec.rb
index 9a5ff2e4518..868408f7beb 100644
--- a/spec/views/search/_filter.html.haml_spec.rb
+++ b/spec/views/search/_filter.html.haml_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe 'search/_filter' do
expect(rendered).to have_selector('input#js-search-group-dropdown')
expect(rendered).to have_selector('label[for="dashboard_search_project"]')
- expect(rendered).to have_selector('button#dashboard_search_project')
+ expect(rendered).to have_selector('input#js-search-project-dropdown')
end
end
end
diff --git a/spec/views/search/_results.html.haml_spec.rb b/spec/views/search/_results.html.haml_spec.rb
index 6299fd0cf36..8960d096143 100644
--- a/spec/views/search/_results.html.haml_spec.rb
+++ b/spec/views/search/_results.html.haml_spec.rb
@@ -3,17 +3,23 @@
require 'spec_helper'
RSpec.describe 'search/_results' do
+ let(:user) { create(:user) }
let(:search_objects) { Issue.page(1).per(2) }
let(:scope) { 'issues' }
+ let(:term) { 'foo' }
before do
controller.params[:action] = 'show'
+ controller.params[:search] = term
create_list(:issue, 3)
@search_objects = search_objects
@scope = scope
- @search_term = 'foo'
+ @search_term = term
+ @search_service = SearchServicePresenter.new(SearchService.new(user, search: term, scope: scope))
+
+ allow(@search_service).to receive(:search_objects).and_return(search_objects)
end
it 'displays the page size' do
@@ -48,11 +54,22 @@ RSpec.describe 'search/_results' do
let(:scope) { search_scope }
let(:search_objects) { Gitlab::ProjectSearchResults.new(user, '*', project: project).objects(scope) }
- it 'renders the click text event tracking attributes' do
- render
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it 'renders the click text event tracking attributes' do
+ render
+
+ expect(rendered).to have_selector('[data-track-event=click_text]')
+ expect(rendered).to have_selector('[data-track-property=search_result]')
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it 'does not render the click text event tracking attributes' do
+ render
- expect(rendered).to have_selector('[data-track-event=click_text]')
- expect(rendered).to have_selector('[data-track-property=search_result]')
+ expect(rendered).not_to have_selector('[data-track-event=click_text]')
+ expect(rendered).not_to have_selector('[data-track-property=search_result]')
+ end
end
it 'does render the sidebar' do
@@ -68,11 +85,22 @@ RSpec.describe 'search/_results' do
let(:scope) { search_scope }
let(:search_objects) { Gitlab::ProjectSearchResults.new(user, '*', project: project).objects(scope) }
- it 'renders the click text event tracking attributes' do
- render
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it 'renders the click text event tracking attributes' do
+ render
+
+ expect(rendered).to have_selector('[data-track-event=click_text]')
+ expect(rendered).to have_selector('[data-track-property=search_result]')
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it 'does not render the click text event tracking attributes' do
+ render
- expect(rendered).to have_selector('[data-track-event=click_text]')
- expect(rendered).to have_selector('[data-track-property=search_result]')
+ expect(rendered).not_to have_selector('[data-track-event=click_text]')
+ expect(rendered).not_to have_selector('[data-track-property=search_result]')
+ end
end
it 'does not render the sidebar' do
diff --git a/spec/views/shared/wikis/_sidebar.html.haml_spec.rb b/spec/views/shared/wikis/_sidebar.html.haml_spec.rb
new file mode 100644
index 00000000000..3e691862937
--- /dev/null
+++ b/spec/views/shared/wikis/_sidebar.html.haml_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe 'shared/wikis/_sidebar.html.haml' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:wiki) { Wiki.for_container(project, project.default_owner) }
+
+ before do
+ assign(:wiki, wiki)
+ assign(:project, project)
+ end
+
+ it 'includes a link to clone the repository' do
+ render
+
+ expect(rendered).to have_link('Clone repository')
+ end
+
+ context 'the sidebar failed to load' do
+ before do
+ assign(:sidebar_error, Object.new)
+ end
+
+ it 'reports this to the user' do
+ render
+
+ expect(rendered).to include('The sidebar failed to load')
+ expect(rendered).to have_css('.gl-alert.gl-alert-info')
+ end
+ end
+
+ context 'The sidebar comes from a custom page' do
+ before do
+ assign(:sidebar_page, double('WikiPage', path: 'sidebar.md', slug: 'sidebar', content: 'Some sidebar content'))
+ end
+
+ it 'does not show an alert' do
+ render
+
+ expect(rendered).not_to include('The sidebar failed to load')
+ expect(rendered).not_to have_css('.gl-alert.gl-alert-info')
+ end
+
+ it 'renders the wiki content' do
+ render
+
+ expect(rendered).to include('Some sidebar content')
+ end
+ end
+
+ context 'The sidebar comes a list of wiki pages' do
+ before do
+ assign(:sidebar_wiki_entries, create_list(:wiki_page, 3, wiki: wiki))
+ assign(:sidebar_limited, true)
+ stub_template "../shared/wikis/_wiki_pages.html.erb" => "Entries: <%= @sidebar_wiki_entries.size %>"
+ stub_template "../shared/wikis/_wiki_page.html.erb" => 'A WIKI PAGE'
+ end
+
+ it 'does not show an alert' do
+ render
+
+ expect(rendered).not_to include('The sidebar failed to load')
+ expect(rendered).not_to have_css('.gl-alert.gl-alert-info')
+ end
+
+ it 'renders the wiki content' do
+ render
+
+ expect(rendered).to include('A WIKI PAGE' * 3)
+ expect(rendered).to have_link('View All Pages')
+ end
+
+ context 'there is no more to see' do
+ it 'does not invite the user to view more' do
+ assign(:sidebar_limited, false)
+
+ render
+
+ expect(rendered).not_to have_link('View All Pages')
+ end
+ end
+ end
+end
diff --git a/spec/workers/approve_blocked_pending_approval_users_worker_spec.rb b/spec/workers/approve_blocked_pending_approval_users_worker_spec.rb
new file mode 100644
index 00000000000..bd603bd870d
--- /dev/null
+++ b/spec/workers/approve_blocked_pending_approval_users_worker_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ApproveBlockedPendingApprovalUsersWorker, type: :worker do
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:active_user) { create(:user) }
+ let_it_be(:blocked_user) { create(:user, state: 'blocked_pending_approval') }
+
+ describe '#perform' do
+ subject do
+ described_class.new.perform(admin.id)
+ end
+
+ it 'calls ApproveService for users in blocked_pending_approval state' do
+ expect_next_instance_of(Users::ApproveService, admin) do |service|
+ expect(service).to receive(:execute).with(blocked_user)
+ end
+
+ subject
+ end
+
+ it 'does not call ApproveService for active users' do
+ expect_next_instance_of(Users::ApproveService, admin) do |service|
+ expect(service).not_to receive(:execute).with(active_user)
+ end
+
+ subject
+ end
+ end
+end
diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb
index b0058c76e27..02e15b7bc22 100644
--- a/spec/workers/build_finished_worker_spec.rb
+++ b/spec/workers/build_finished_worker_spec.rb
@@ -26,9 +26,6 @@ RSpec.describe BuildFinishedWorker do
expect_next_instance_of(Ci::BuildReportResultWorker) do |instance|
expect(instance).to receive(:perform)
end
- expect_next_instance_of(Ci::TestCasesService) do |instance|
- expect(instance).to receive(:execute)
- end
expect(BuildHooksWorker).to receive(:perform_async)
expect(ExpirePipelineCacheWorker).to receive(:perform_async)
diff --git a/spec/workers/ci/test_failure_history_worker_spec.rb b/spec/workers/ci/test_failure_history_worker_spec.rb
new file mode 100644
index 00000000000..d2896c08209
--- /dev/null
+++ b/spec/workers/ci/test_failure_history_worker_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Ci::TestFailureHistoryWorker do
+ describe '#perform' do
+ subject(:perform) { described_class.new.perform(pipeline_id) }
+
+ context 'when pipeline exists' do
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:pipeline_id) { pipeline.id }
+
+ it 'executes test failure history service' do
+ expect_next_instance_of(::Ci::TestFailureHistoryService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ perform
+ end
+ end
+
+ context 'when pipeline does not exist' do
+ let(:pipeline_id) { non_existing_record_id }
+
+ it 'does not execute test failure history service' do
+ expect(Ci::TestFailureHistoryService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+
+ include_examples 'an idempotent worker' do
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:job_args) { [pipeline.id] }
+
+ it 'tracks test failures' do
+ # The test report has 2 test case failures
+ create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: pipeline.project)
+
+ subject
+
+ expect(Ci::TestCase.count).to eq(2)
+ expect(Ci::TestCaseFailure.count).to eq(2)
+ end
+ end
+end
diff --git a/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb b/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb
index 5a37031a55a..fb779bf3b01 100644
--- a/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb
+++ b/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Clusters::Applications::CheckPrometheusHealthWorker, '#perform' d
it 'triggers health service' do
cluster = create(:cluster)
allow(Gitlab::Monitor::DemoProjects).to receive(:primary_keys)
- allow(Clusters::Cluster).to receive_message_chain(:with_application_prometheus, :with_project_alert_service_data).and_return([cluster])
+ allow(Clusters::Cluster).to receive_message_chain(:with_application_prometheus, :with_project_http_integrations).and_return([cluster])
service_instance = instance_double(Clusters::Applications::PrometheusHealthCheckService)
expect(Clusters::Applications::PrometheusHealthCheckService).to receive(:new).with(cluster).and_return(service_instance)
diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
index d0cbc6b35e2..75f2c7922de 100644
--- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
@@ -22,20 +22,21 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do
end
describe '#import' do
- it 'imports the object' do
- representation_class = double(:representation_class)
- importer_class = double(:importer_class)
- importer_instance = double(:importer_instance)
- representation = double(:representation)
- project = double(:project, full_path: 'foo/bar')
- client = double(:client)
-
+ let(:representation_class) { double(:representation_class) }
+ let(:importer_class) { double(:importer_class, name: 'klass_name') }
+ let(:importer_instance) { double(:importer_instance) }
+ let(:representation) { double(:representation) }
+ let(:project) { double(:project, full_path: 'foo/bar', id: 1) }
+ let(:client) { double(:client) }
+
+ before do
expect(worker)
.to receive(:representation_class)
.and_return(representation_class)
expect(worker)
.to receive(:importer_class)
+ .at_least(:once)
.and_return(importer_class)
expect(representation_class)
@@ -47,7 +48,9 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do
.to receive(:new)
.with(representation, project, client)
.and_return(importer_instance)
+ end
+ it 'imports the object' do
expect(importer_instance)
.to receive(:execute)
@@ -55,8 +58,61 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do
.to receive(:increment)
.and_call_original
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'starting importer',
+ import_source: :github,
+ project_id: 1,
+ importer: 'klass_name'
+ )
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'importer finished',
+ import_source: :github,
+ project_id: 1,
+ importer: 'klass_name'
+ )
+ end
+
worker.import(project, client, { 'number' => 10 })
end
+
+ it 'logs error when the import fails' do
+ exception = StandardError.new('some error')
+ expect(importer_instance)
+ .to receive(:execute)
+ .and_raise(exception)
+
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'starting importer',
+ import_source: :github,
+ project_id: project.id,
+ importer: 'klass_name'
+ )
+ expect(logger)
+ .to receive(:error)
+ .with(
+ message: 'importer failed',
+ import_source: :github,
+ project_id: project.id,
+ importer: 'klass_name',
+ 'error.message': 'some error'
+ )
+ end
+
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_and_raise_exception)
+ .with(exception, import_source: :github, project_id: 1, importer: 'klass_name')
+ .and_call_original
+
+ expect { worker.import(project, client, { 'number' => 10 }) }.to raise_error(exception)
+ end
end
describe '#counter' do
diff --git a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
index 03e875bcb87..651ea77a71c 100644
--- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
@@ -5,7 +5,13 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::StageMethods do
let(:project) { create(:project) }
let(:worker) do
- Class.new { include(Gitlab::GithubImport::StageMethods) }.new
+ Class.new do
+ def self.name
+ 'DummyStage'
+ end
+
+ include(Gitlab::GithubImport::StageMethods)
+ end.new
end
describe '#perform' do
@@ -30,8 +36,72 @@ RSpec.describe Gitlab::GithubImport::StageMethods do
an_instance_of(Project)
)
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'starting stage',
+ import_source: :github,
+ project_id: project.id,
+ import_stage: 'DummyStage'
+ )
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'stage finished',
+ import_source: :github,
+ project_id: project.id,
+ import_stage: 'DummyStage'
+ )
+ end
+
worker.perform(project.id)
end
+
+ it 'logs error when import fails' do
+ exception = StandardError.new('some error')
+
+ allow(worker)
+ .to receive(:find_project)
+ .with(project.id)
+ .and_return(project)
+
+ expect(worker)
+ .to receive(:try_import)
+ .and_raise(exception)
+
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'starting stage',
+ import_source: :github,
+ project_id: project.id,
+ import_stage: 'DummyStage'
+ )
+ expect(logger)
+ .to receive(:error)
+ .with(
+ message: 'stage failed',
+ import_source: :github,
+ project_id: project.id,
+ import_stage: 'DummyStage',
+ 'error.message': 'some error'
+ )
+ end
+
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_and_raise_exception)
+ .with(
+ exception,
+ import_source: :github,
+ project_id: project.id,
+ import_stage: 'DummyStage'
+ )
+ .and_call_original
+
+ expect { worker.perform(project.id) }.to raise_error(exception)
+ end
end
describe '#try_import' do
diff --git a/spec/workers/concerns/reenqueuer_spec.rb b/spec/workers/concerns/reenqueuer_spec.rb
index ab44042834f..56db2239bb1 100644
--- a/spec/workers/concerns/reenqueuer_spec.rb
+++ b/spec/workers/concerns/reenqueuer_spec.rb
@@ -79,6 +79,10 @@ RSpec.describe Reenqueuer do
job.perform
end
+
+ it 'returns the original value from #perform' do
+ expect(job.perform).to eq(true)
+ end
end
context 'when #perform returns falsey' do
@@ -87,6 +91,10 @@ RSpec.describe Reenqueuer do
job.perform
end
+
+ it 'returns the original value from #perform' do
+ expect(job.perform).to eq(false)
+ end
end
end
end
diff --git a/spec/workers/environments/canary_ingress/update_worker_spec.rb b/spec/workers/environments/canary_ingress/update_worker_spec.rb
new file mode 100644
index 00000000000..7bc5108719c
--- /dev/null
+++ b/spec/workers/environments/canary_ingress/update_worker_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Environments::CanaryIngress::UpdateWorker do
+ let_it_be(:environment) { create(:environment) }
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ subject { worker.perform(environment_id, params) }
+
+ let(:environment_id) { environment.id }
+ let(:params) { { 'weight' => 50 } }
+
+ it 'executes the update service' do
+ expect_next_instance_of(Environments::CanaryIngress::UpdateService, environment.project, nil, params) do |service|
+ expect(service).to receive(:execute).with(environment)
+ end
+
+ subject
+ end
+
+ context 'when an environment does not exist' do
+ let(:environment_id) { non_existing_record_id }
+
+ it 'does not execute the update service' do
+ expect(Environments::CanaryIngress::UpdateService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
index 211eba993f7..4039cdac721 100644
--- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::GithubImport::ImportDiffNoteWorker do
describe '#import' do
it 'imports a diff note' do
- project = double(:project, full_path: 'foo/bar')
+ project = double(:project, full_path: 'foo/bar', id: 1)
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
index 1d285790ee2..c25e89f6928 100644
--- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::GithubImport::ImportIssueWorker do
describe '#import' do
it 'imports an issue' do
- project = double(:project, full_path: 'foo/bar')
+ project = double(:project, full_path: 'foo/bar', id: 1)
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
index 618aca4ff0a..bfb40d7c3d3 100644
--- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::GithubImport::ImportNoteWorker do
describe '#import' do
it 'imports a note' do
- project = double(:project, full_path: 'foo/bar')
+ project = double(:project, full_path: 'foo/bar', id: 1)
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb
new file mode 100644
index 00000000000..c799c676300
--- /dev/null
+++ b/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::ImportPullRequestMergedByWorker do
+ it { is_expected.to include_module(Gitlab::GithubImport::ObjectImporter) }
+
+ describe '#representation_class' do
+ it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::PullRequest) }
+ end
+
+ describe '#importer_class' do
+ it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequestMergedByImporter) }
+ end
+
+ describe '#counter_name' do
+ it { expect(subject.counter_name).to eq(:github_importer_imported_pull_requests_merged_by) }
+ end
+
+ describe '#counter_description' do
+ it { expect(subject.counter_description).to eq('The number of imported GitHub pull requests merged by') }
+ end
+end
diff --git a/spec/workers/gitlab/github_import/import_pull_request_review_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_review_worker_spec.rb
new file mode 100644
index 00000000000..cd14d6631d5
--- /dev/null
+++ b/spec/workers/gitlab/github_import/import_pull_request_review_worker_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::ImportPullRequestReviewWorker do
+ it { is_expected.to include_module(Gitlab::GithubImport::ObjectImporter) }
+
+ describe '#representation_class' do
+ it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::PullRequestReview) }
+ end
+
+ describe '#importer_class' do
+ it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequestReviewImporter) }
+ end
+
+ describe '#counter_name' do
+ it { expect(subject.counter_name).to eq(:github_importer_imported_pull_request_reviews) }
+ end
+
+ describe '#counter_description' do
+ it { expect(subject.counter_description).to eq('The number of imported GitHub pull request reviews') }
+ end
+end
diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
index 0f5df302d56..12b21abf910 100644
--- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::GithubImport::ImportPullRequestWorker do
describe '#import' do
it 'imports a pull request' do
- project = double(:project, full_path: 'foo/bar')
+ project = double(:project, full_path: 'foo/bar', id: 1)
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
index c821e0aaa09..2615da2be15 100644
--- a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
@@ -26,7 +26,17 @@ RSpec.describe Gitlab::GithubImport::Stage::FinishImportWorker do
.to receive(:increment)
.and_call_original
- expect(worker.logger).to receive(:info).with(an_instance_of(String))
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'GitHub project import finished',
+ import_stage: 'Gitlab::GithubImport::Stage::FinishImportWorker',
+ import_source: :github,
+ project_id: project.id,
+ duration_s: a_kind_of(Numeric)
+ )
+ end
worker.report_import_time(project)
end
diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb
new file mode 100644
index 00000000000..6fcb5db2a54
--- /dev/null
+++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Stage::ImportPullRequestsMergedByWorker do
+ let(:project) { create(:project) }
+ let(:import_state) { create(:import_state, project: project) }
+ let(:worker) { described_class.new }
+
+ describe '#import' do
+ it 'imports all the pull requests' do
+ importer = double(:importer)
+ client = double(:client)
+ waiter = Gitlab::JobWaiter.new(2, '123')
+
+ expect(Gitlab::GithubImport::Importer::PullRequestsMergedByImporter)
+ .to receive(:new)
+ .with(project, client)
+ .and_return(importer)
+
+ expect(importer)
+ .to receive(:execute)
+ .and_return(waiter)
+
+ expect(import_state)
+ .to receive(:refresh_jid_expiration)
+
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 2 }, :pull_request_reviews)
+
+ worker.import(client, project)
+ end
+ end
+end
diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_reviews_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_reviews_worker_spec.rb
new file mode 100644
index 00000000000..7acf1a338d3
--- /dev/null
+++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_reviews_worker_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Stage::ImportPullRequestsReviewsWorker do
+ let(:project) { create(:project) }
+ let(:import_state) { create(:import_state, project: project) }
+ let(:worker) { described_class.new }
+ let(:client) { double(:client) }
+
+ describe '#import' do
+ it 'does not import with the feature disabled' do
+ stub_feature_flags(github_import_pull_request_reviews: false)
+
+ expect(Gitlab::JobWaiter)
+ .to receive(:new)
+ .and_return(double(key: '123', jobs_remaining: 0))
+
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 0 }, :issues_and_diff_notes)
+
+ worker.import(client, project)
+ end
+
+ it 'imports all the pull request reviews' do
+ stub_feature_flags(github_import_pull_request_reviews: true)
+
+ importer = double(:importer)
+
+ waiter = Gitlab::JobWaiter.new(2, '123')
+
+ expect(Gitlab::GithubImport::Importer::PullRequestsReviewsImporter)
+ .to receive(:new)
+ .with(project, client)
+ .and_return(importer)
+
+ expect(importer)
+ .to receive(:execute)
+ .and_return(waiter)
+
+ expect(import_state)
+ .to receive(:refresh_jid_expiration)
+
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 2 }, :issues_and_diff_notes)
+
+ worker.import(client, project)
+ end
+ end
+end
diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
index 0acbca7032c..29578f9bf37 100644
--- a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
expect(Gitlab::GithubImport::AdvanceStageWorker)
.to receive(:perform_async)
- .with(project.id, { '123' => 2 }, :issues_and_diff_notes)
+ .with(project.id, { '123' => 2 }, :pull_requests_merged_by)
worker.import(client, project)
end
diff --git a/spec/workers/gitlab_performance_bar_stats_worker_spec.rb b/spec/workers/gitlab_performance_bar_stats_worker_spec.rb
new file mode 100644
index 00000000000..367003dd1ad
--- /dev/null
+++ b/spec/workers/gitlab_performance_bar_stats_worker_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabPerformanceBarStatsWorker do
+ include ExclusiveLeaseHelpers
+
+ subject(:worker) { described_class.new }
+
+ describe '#perform' do
+ let(:redis) { double(Gitlab::Redis::SharedState) }
+ let(:uuid) { 1 }
+
+ before do
+ expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
+ expect_to_cancel_exclusive_lease(GitlabPerformanceBarStatsWorker::LEASE_KEY, uuid)
+ end
+
+ it 'fetches list of request ids and processes them' do
+ expect(redis).to receive(:smembers).with(GitlabPerformanceBarStatsWorker::STATS_KEY).and_return([1, 2])
+ expect(redis).to receive(:del).with(GitlabPerformanceBarStatsWorker::STATS_KEY)
+ expect_next_instance_of(Gitlab::PerformanceBar::Stats) do |stats|
+ expect(stats).to receive(:process).with(1)
+ expect(stats).to receive(:process).with(2)
+ end
+
+ worker.perform(uuid)
+ end
+ end
+end
diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb
index a180d29fd5f..f282b20363c 100644
--- a/spec/workers/gitlab_usage_ping_worker_spec.rb
+++ b/spec/workers/gitlab_usage_ping_worker_spec.rb
@@ -8,6 +8,13 @@ RSpec.describe GitlabUsagePingWorker, :clean_gitlab_redis_shared_state do
allow(subject).to receive(:sleep)
end
+ it 'does not run for GitLab.com' do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ expect(SubmitUsagePingService).not_to receive(:new)
+
+ subject.perform
+ end
+
it 'delegates to SubmitUsagePingService' do
expect_next_instance_of(SubmitUsagePingService) { |service| expect(service).to receive(:execute) }
diff --git a/spec/workers/import_issues_csv_worker_spec.rb b/spec/workers/import_issues_csv_worker_spec.rb
index c5420b00e8a..6a698af49c0 100644
--- a/spec/workers/import_issues_csv_worker_spec.rb
+++ b/spec/workers/import_issues_csv_worker_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe ImportIssuesCsvWorker do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:upload) { create(:upload) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let(:upload) { create(:upload, :with_file) }
let(:worker) { described_class.new }
@@ -19,5 +19,9 @@ RSpec.describe ImportIssuesCsvWorker do
expect { upload.reload }.to raise_error ActiveRecord::RecordNotFound
end
+
+ it_behaves_like 'an idempotent worker' do
+ let(:job_args) { [user.id, project.id, upload.id] }
+ end
end
end
diff --git a/spec/workers/jira_connect/sync_branch_worker_spec.rb b/spec/workers/jira_connect/sync_branch_worker_spec.rb
index 4aa2f89de7b..c8453064b0d 100644
--- a/spec/workers/jira_connect/sync_branch_worker_spec.rb
+++ b/spec/workers/jira_connect/sync_branch_worker_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe JiraConnect::SyncBranchWorker do
+ include AfterNextHelpers
+
describe '#perform' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group) }
@@ -67,7 +69,7 @@ RSpec.describe JiraConnect::SyncBranchWorker do
context 'with update_sequence_id' do
let(:update_sequence_id) { 1 }
- let(:request_url) { 'https://sample.atlassian.net/rest/devinfo/0.10/bulk' }
+ let(:request_path) { '/rest/devinfo/0.10/bulk' }
let(:request_body) do
{
repositories: [
@@ -78,14 +80,13 @@ RSpec.describe JiraConnect::SyncBranchWorker do
update_sequence_id: update_sequence_id
)
]
- }.to_json
+ }
end
subject { described_class.new.perform(project_id, branch_name, commit_shas, update_sequence_id) }
it 'sends the reqeust with custom update_sequence_id' do
- expect(Atlassian::JiraConnect::Client).to receive(:post)
- .with(URI(request_url), headers: anything, body: request_body)
+ expect_next(Atlassian::JiraConnect::Client).to receive(:post).with(request_path, request_body)
subject
end
diff --git a/spec/workers/jira_connect/sync_builds_worker_spec.rb b/spec/workers/jira_connect/sync_builds_worker_spec.rb
new file mode 100644
index 00000000000..7c58803d778
--- /dev/null
+++ b/spec/workers/jira_connect/sync_builds_worker_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::JiraConnect::SyncBuildsWorker do
+ include AfterNextHelpers
+ include ServicesHelper
+
+ describe '#perform' do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ let(:sequence_id) { Random.random_number(1..10_000) }
+ let(:pipeline_id) { pipeline.id }
+
+ subject { described_class.new.perform(pipeline_id, sequence_id) }
+
+ context 'when pipeline exists' do
+ it 'calls the Jira sync service' do
+ expect_next(::JiraConnect::SyncService, pipeline.project)
+ .to receive(:execute).with(pipelines: contain_exactly(pipeline), update_sequence_id: sequence_id)
+
+ subject
+ end
+ end
+
+ context 'when pipeline does not exist' do
+ let(:pipeline_id) { non_existing_record_id }
+
+ it 'does not call the sync service' do
+ expect_next(::JiraConnect::SyncService).not_to receive(:execute)
+
+ subject
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(jira_sync_builds: false)
+ end
+
+ it 'does not call the sync service' do
+ expect_next(::JiraConnect::SyncService).not_to receive(:execute)
+
+ subject
+ end
+ end
+
+ context 'when the feature flag is enabled for this project' do
+ before do
+ stub_feature_flags(jira_sync_builds: pipeline.project)
+ end
+
+ it 'calls the sync service' do
+ expect_next(::JiraConnect::SyncService).to receive(:execute)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/workers/jira_connect/sync_merge_request_worker_spec.rb b/spec/workers/jira_connect/sync_merge_request_worker_spec.rb
index b3c0db4f260..1a40aa2b3ad 100644
--- a/spec/workers/jira_connect/sync_merge_request_worker_spec.rb
+++ b/spec/workers/jira_connect/sync_merge_request_worker_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe JiraConnect::SyncMergeRequestWorker do
+ include AfterNextHelpers
+
describe '#perform' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group) }
@@ -33,7 +35,7 @@ RSpec.describe JiraConnect::SyncMergeRequestWorker do
context 'with update_sequence_id' do
let(:update_sequence_id) { 1 }
- let(:request_url) { 'https://sample.atlassian.net/rest/devinfo/0.10/bulk' }
+ let(:request_path) { '/rest/devinfo/0.10/bulk' }
let(:request_body) do
{
repositories: [
@@ -43,14 +45,13 @@ RSpec.describe JiraConnect::SyncMergeRequestWorker do
update_sequence_id: update_sequence_id
)
]
- }.to_json
+ }
end
subject { described_class.new.perform(merge_request_id, update_sequence_id) }
it 'sends the request with custom update_sequence_id' do
- expect(Atlassian::JiraConnect::Client).to receive(:post)
- .with(URI(request_url), headers: anything, body: request_body)
+ expect_next(Atlassian::JiraConnect::Client).to receive(:post).with(request_path, request_body)
subject
end
diff --git a/spec/workers/jira_connect/sync_project_worker_spec.rb b/spec/workers/jira_connect/sync_project_worker_spec.rb
index 25210de828c..f7fa565d534 100644
--- a/spec/workers/jira_connect/sync_project_worker_spec.rb
+++ b/spec/workers/jira_connect/sync_project_worker_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe JiraConnect::SyncProjectWorker, factory_default: :keep do
end
it_behaves_like 'an idempotent worker' do
- let(:request_url) { 'https://sample.atlassian.net/rest/devinfo/0.10/bulk' }
+ let(:request_path) { '/rest/devinfo/0.10/bulk' }
let(:request_body) do
{
repositories: [
@@ -46,13 +46,13 @@ RSpec.describe JiraConnect::SyncProjectWorker, factory_default: :keep do
update_sequence_id: update_sequence_id
)
]
- }.to_json
+ }
end
it 'sends the request with custom update_sequence_id' do
- expect(Atlassian::JiraConnect::Client).to receive(:post)
- .exactly(IdempotentWorkerHelper::WORKER_EXEC_TIMES).times
- .with(URI(request_url), headers: anything, body: request_body)
+ allow_next_instances_of(Atlassian::JiraConnect::Client, IdempotentWorkerHelper::WORKER_EXEC_TIMES) do |client|
+ expect(client).to receive(:post).with(request_path, request_body)
+ end
subject
end
diff --git a/spec/workers/member_invitation_reminder_emails_worker_spec.rb b/spec/workers/member_invitation_reminder_emails_worker_spec.rb
index bfd08792c7c..bb4ff466584 100644
--- a/spec/workers/member_invitation_reminder_emails_worker_spec.rb
+++ b/spec/workers/member_invitation_reminder_emails_worker_spec.rb
@@ -10,30 +10,12 @@ RSpec.describe MemberInvitationReminderEmailsWorker do
create(:group_member, :invited, created_at: 2.days.ago)
end
- context 'feature flag disabled' do
- before do
- stub_experiment(invitation_reminders: false)
+ it 'executes the invitation reminder email service' do
+ expect_next_instance_of(Members::InvitationReminderEmailService) do |service|
+ expect(service).to receive(:execute)
end
- it 'does not attempt to execute the invitation reminder service' do
- expect(Members::InvitationReminderEmailService).not_to receive(:new)
-
- subject
- end
- end
-
- context 'feature flag enabled' do
- before do
- stub_experiment(invitation_reminders: true)
- end
-
- it 'executes the invitation reminder email service' do
- expect_next_instance_of(Members::InvitationReminderEmailService) do |service|
- expect(service).to receive(:execute)
- end
-
- subject
- end
+ subject
end
end
end
diff --git a/spec/workers/namespaces/onboarding_user_added_worker_spec.rb b/spec/workers/namespaces/onboarding_user_added_worker_spec.rb
new file mode 100644
index 00000000000..03c668259f8
--- /dev/null
+++ b/spec/workers/namespaces/onboarding_user_added_worker_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Namespaces::OnboardingUserAddedWorker, '#perform' do
+ include AfterNextHelpers
+
+ let_it_be(:group) { create(:group) }
+
+ it 'records the event' do
+ expect_next(OnboardingProgressService, group)
+ .to receive(:execute).with(action: :user_added).and_call_original
+
+ expect { subject.perform(group.id) }.to change(NamespaceOnboardingAction, :count).by(1)
+ end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 77c1d16428f..5b8d8878a99 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -3,6 +3,9 @@
require 'spec_helper'
RSpec.describe PostReceive do
+ include AfterNextHelpers
+ include ServicesHelper
+
let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
@@ -18,8 +21,8 @@ RSpec.describe PostReceive do
described_class.new.perform(gl_repository, key_id, changes)
end
- context "as a sidekiq worker" do
- it "responds to #perform" do
+ context 'as a sidekiq worker' do
+ it 'responds to #perform' do
expect(described_class.new).to respond_to(:perform)
end
end
@@ -30,7 +33,7 @@ RSpec.describe PostReceive do
"Triggered hook for non-existing gl_repository \"#{gl_repository}\""
end
- it "returns false and logs an error" do
+ it 'returns false and logs an error' do
expect(Gitlab::GitLogger).to receive(:error).with("POST-RECEIVE: #{error_message}")
expect(perform).to be(false)
end
@@ -42,9 +45,7 @@ RSpec.describe PostReceive do
it 'does not log an error' do
expect(Gitlab::GitLogger).not_to receive(:error)
expect(Gitlab::GitPostReceive).to receive(:new).and_call_original
- expect_any_instance_of(described_class) do |instance|
- expect(instance).to receive(:process_snippet_changes)
- end
+ expect_next(described_class).to receive(:process_snippet_changes)
perform
end
@@ -61,13 +62,13 @@ RSpec.describe PostReceive do
end
end
- describe "#process_project_changes" do
+ describe '#process_project_changes' do
context 'with an empty project' do
let(:empty_project) { create(:project, :empty_repo) }
let(:changes) { "123456 789012 refs/heads/tést1\n" }
before do
- allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(empty_project.owner)
+ allow_next(Gitlab::GitPostReceive).to receive(:identify).and_return(empty_project.owner)
# Need to mock here so we can expect calls on project
allow(Gitlab::GlRepository).to receive(:parse).and_return([empty_project, empty_project, Gitlab::GlRepository::PROJECT])
end
@@ -97,9 +98,9 @@ RSpec.describe PostReceive do
end
context 'empty changes' do
- it "does not call any PushService but runs after project hooks" do
+ it 'does not call any PushService but runs after project hooks' do
expect(Git::ProcessRefChangesService).not_to receive(:new)
- expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) }
+ expect_next(SystemHooksService).to receive(:execute_hooks)
perform(changes: "")
end
@@ -121,7 +122,7 @@ RSpec.describe PostReceive do
let(:push_service) { double(execute: true) }
before do
- allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner)
+ allow_next(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner)
allow(Gitlab::GlRepository).to receive(:parse).and_return([project, project, Gitlab::GlRepository::PROJECT])
end
@@ -135,7 +136,7 @@ RSpec.describe PostReceive do
end
end
- context "branches" do
+ context 'branches' do
let(:changes) do
<<~EOF
123456 789012 refs/heads/tést1
@@ -157,9 +158,7 @@ RSpec.describe PostReceive do
end
it 'calls Git::ProcessRefChangesService' do
- expect_next_instance_of(Git::ProcessRefChangesService) do |service|
- expect(service).to receive(:execute).and_return(true)
- end
+ expect_next(Git::ProcessRefChangesService).to receive(:execute).and_return(true)
perform
end
@@ -191,7 +190,7 @@ RSpec.describe PostReceive do
end
end
- context "tags" do
+ context 'tags' do
let(:changes) do
<<~EOF
654321 210987 refs/tags/tag1
@@ -219,9 +218,7 @@ RSpec.describe PostReceive do
end
it 'calls Git::ProcessRefChangesService' do
- expect_next_instance_of(Git::ProcessRefChangesService) do |service|
- expect(service).to receive(:execute).and_return(true)
- end
+ expect_execution_of(Git::ProcessRefChangesService)
perform
end
@@ -236,7 +233,7 @@ RSpec.describe PostReceive do
it_behaves_like 'updating remote mirrors'
end
- context "merge-requests" do
+ context 'merge-requests' do
let(:changes) { "123456 789012 refs/merge-requests/123" }
it "does not call any of the services" do
@@ -250,19 +247,19 @@ RSpec.describe PostReceive do
context 'after project changes hooks' do
let(:changes) { '123456 789012 refs/heads/tést' }
- let(:fake_hook_data) { Hash.new(event_name: 'repository_update') }
+ let(:fake_hook_data) { { event_name: 'repository_update' } }
before do
- allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
+ allow(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
# silence hooks so we can isolate
- allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true)
- expect_next_instance_of(Git::ProcessRefChangesService) do |service|
- expect(service).to receive(:execute).and_return(true)
- end
+ allow_next(Key).to receive(:post_create_hook).and_return(true)
+ expect_execution_of(Git::ProcessRefChangesService)
end
it 'calls SystemHooksService' do
- expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true)
+ expect_next(SystemHooksService)
+ .to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks)
+ .and_return(true)
perform
end
@@ -299,7 +296,7 @@ RSpec.describe PostReceive do
end
end
- context "master" do
+ context 'master' do
let(:default_branch) { 'master' }
let(:oldrev) { '012345' }
let(:newrev) { '6789ab' }
@@ -313,18 +310,13 @@ RSpec.describe PostReceive do
let(:raw_repo) { double('RawRepo') }
it 'processes the changes on the master branch' do
- expect_next_instance_of(Git::WikiPushService) do |service|
- expect(service).to receive(:execute).and_call_original
- end
- expect(project.wiki).to receive(:default_branch).twice.and_return(default_branch)
- expect(project.wiki.repository).to receive(:raw).and_return(raw_repo)
- expect(raw_repo).to receive(:raw_changes_between).once.with(oldrev, newrev).and_return([])
+ expect_next(Git::WikiPushService).to receive(:execute)
perform
end
end
- context "branches" do
+ context 'branches' do
let(:changes) do
<<~EOF
123456 789012 refs/heads/tést1
@@ -333,9 +325,7 @@ RSpec.describe PostReceive do
end
before do
- allow_next_instance_of(Git::WikiPushService) do |service|
- allow(service).to receive(:execute)
- end
+ allow_next(Git::WikiPushService).to receive(:execute)
end
it 'expires the branches cache' do
@@ -353,24 +343,21 @@ RSpec.describe PostReceive do
end
end
- context "webhook" do
- it "fetches the correct project" do
+ context 'webhook' do
+ it 'fetches the correct project' do
expect(Project).to receive(:find_by).with(id: project.id)
perform
end
it "does not run if the author is not in the project" do
- allow_any_instance_of(Gitlab::GitPostReceive)
- .to receive(:identify_using_ssh_key)
- .and_return(nil)
-
+ allow_next(Gitlab::GitPostReceive).to receive(:identify_using_ssh_key).and_return(nil)
expect(project).not_to receive(:execute_hooks)
expect(perform).to be_falsey
end
- it "asks the project to trigger all hooks" do
+ it 'asks the project to trigger all hooks' do
create(:project_hook, push_events: true, tag_push_events: true, project: project)
create(:custom_issue_tracker_service, push_events: true, merge_requests_events: false, project: project)
allow(Project).to receive(:find_by).and_return(project)
@@ -381,11 +368,9 @@ RSpec.describe PostReceive do
perform
end
- it "enqueues a UpdateMergeRequestsWorker job" do
+ it 'enqueues a UpdateMergeRequestsWorker job' do
allow(Project).to receive(:find_by).and_return(project)
- expect_next_instance_of(MergeRequests::PushedBranchesService) do |service|
- expect(service).to receive(:execute).and_return(%w(tést))
- end
+ expect_next(MergeRequests::PushedBranchesService).to receive(:execute).and_return(%w(tést))
expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args)
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index 0f91f7af255..7f42c700ce4 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -5,8 +5,9 @@ require 'spec_helper'
RSpec.describe ProjectCacheWorker do
include ExclusiveLeaseHelpers
+ let_it_be(:project) { create(:project, :repository) }
+
let(:worker) { described_class.new }
- let(:project) { create(:project, :repository) }
let(:lease_key) { ["project_cache_worker", project.id, *statistics.sort].join(":") }
let(:lease_timeout) { ProjectCacheWorker::LEASE_TIMEOUT }
let(:statistics) { [] }
@@ -126,4 +127,15 @@ RSpec.describe ProjectCacheWorker do
end
end
end
+
+ it_behaves_like 'an idempotent worker' do
+ let(:job_args) { [project.id, %w(readme), %w(repository_size)] }
+
+ it 'calls Projects::UpdateStatisticsService service twice', :clean_gitlab_redis_shared_state do
+ expect(Projects::UpdateStatisticsService).to receive(:new).once.and_return(double(execute: true))
+ expect(UpdateProjectStatisticsWorker).to receive(:perform_in).once
+
+ subject
+ end
+ end
end
diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb
index defecefc3cc..9923d8bde7f 100644
--- a/spec/workers/project_export_worker_spec.rb
+++ b/spec/workers/project_export_worker_spec.rb
@@ -3,84 +3,5 @@
require 'spec_helper'
RSpec.describe ProjectExportWorker do
- let!(:user) { create(:user) }
- let!(:project) { create(:project) }
-
- subject { described_class.new }
-
- describe '#perform' do
- before do
- allow_next_instance_of(described_class) do |job|
- allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
- end
- end
-
- context 'when it succeeds' do
- it 'calls the ExportService' do
- expect_next_instance_of(::Projects::ImportExport::ExportService) do |service|
- expect(service).to receive(:execute)
- end
-
- subject.perform(user.id, project.id, { 'klass' => 'Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy' })
- end
-
- context 'export job' do
- before do
- allow_next_instance_of(::Projects::ImportExport::ExportService) do |service|
- allow(service).to receive(:execute)
- end
- end
-
- it 'creates an export job record for the project' do
- expect { subject.perform(user.id, project.id, {}) }.to change { project.export_jobs.count }.from(0).to(1)
- end
-
- it 'sets the export job status to started' do
- expect_next_instance_of(ProjectExportJob) do |job|
- expect(job).to receive(:start)
- end
-
- subject.perform(user.id, project.id, {})
- end
-
- it 'sets the export job status to finished' do
- expect_next_instance_of(ProjectExportJob) do |job|
- expect(job).to receive(:finish)
- end
-
- subject.perform(user.id, project.id, {})
- end
- end
- end
-
- context 'when it fails' do
- it 'does not raise an exception when strategy is invalid' do
- expect(::Projects::ImportExport::ExportService).not_to receive(:new)
-
- expect { subject.perform(user.id, project.id, { 'klass' => 'Whatever' }) }.not_to raise_error
- end
-
- it 'does not raise error when project cannot be found' do
- expect { subject.perform(user.id, non_existing_record_id, {}) }.not_to raise_error
- end
-
- it 'does not raise error when user cannot be found' do
- expect { subject.perform(non_existing_record_id, project.id, {}) }.not_to raise_error
- end
- end
- end
-
- describe 'sidekiq options' do
- it 'disables retry' do
- expect(described_class.sidekiq_options['retry']).to eq(false)
- end
-
- it 'disables dead' do
- expect(described_class.sidekiq_options['dead']).to eq(false)
- end
-
- it 'sets default status expiration' do
- expect(described_class.sidekiq_options['status_expiration']).to eq(StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION)
- end
- end
+ it_behaves_like 'export worker'
end
diff --git a/spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb b/spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb
new file mode 100644
index 00000000000..aadfae51906
--- /dev/null
+++ b/spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ProjectScheduleBulkRepositoryShardMovesWorker do
+ describe "#perform" do
+ before do
+ stub_storage_settings('test_second_storage' => { 'path' => 'tmp/tests/extra_storage' })
+
+ allow(ProjectUpdateRepositoryStorageWorker).to receive(:perform_async)
+ end
+
+ let!(:project) { create(:project, :repository).tap { |project| project.track_project_repository } }
+ let(:source_storage_name) { 'default' }
+ let(:destination_storage_name) { 'test_second_storage' }
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [source_storage_name, destination_storage_name] }
+
+ it 'schedules project repository storage moves' do
+ expect { subject }.to change(ProjectRepositoryStorageMove, :count).by(1)
+
+ storage_move = project.repository_storage_moves.last!
+
+ expect(storage_move).to have_attributes(
+ source_storage_name: source_storage_name,
+ destination_storage_name: destination_storage_name,
+ state_name: :scheduled
+ )
+ end
+ end
+ end
+end
diff --git a/spec/workers/purge_dependency_proxy_cache_worker_spec.rb b/spec/workers/purge_dependency_proxy_cache_worker_spec.rb
index 9cd3b6636f5..8379b11af8f 100644
--- a/spec/workers/purge_dependency_proxy_cache_worker_spec.rb
+++ b/spec/workers/purge_dependency_proxy_cache_worker_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe PurgeDependencyProxyCacheWorker do
let_it_be(:user) { create(:admin) }
let_it_be(:blob) { create(:dependency_proxy_blob )}
let_it_be(:group, reload: true) { blob.group }
+ let_it_be(:manifest) { create(:dependency_proxy_manifest, group: group )}
let_it_be(:group_id) { group.id }
subject { described_class.new.perform(user.id, group_id) }
@@ -17,8 +18,9 @@ RSpec.describe PurgeDependencyProxyCacheWorker do
describe '#perform' do
shared_examples 'returns nil' do
- it 'returns nil' do
+ it 'returns nil', :aggregate_failures do
expect { subject }.not_to change { group.dependency_proxy_blobs.size }
+ expect { subject }.not_to change { group.dependency_proxy_manifests.size }
expect(subject).to be_nil
end
end
@@ -27,12 +29,14 @@ RSpec.describe PurgeDependencyProxyCacheWorker do
include_examples 'an idempotent worker' do
let(:job_args) { [user.id, group_id] }
- it 'deletes the blobs and returns ok' do
+ it 'deletes the blobs and returns ok', :aggregate_failures do
expect(group.dependency_proxy_blobs.size).to eq(1)
+ expect(group.dependency_proxy_manifests.size).to eq(1)
subject
expect(group.dependency_proxy_blobs.size).to eq(0)
+ expect(group.dependency_proxy_manifests.size).to eq(0)
end
end
end
diff --git a/spec/workers/create_evidence_worker_spec.rb b/spec/workers/releases/create_evidence_worker_spec.rb
index c700c086163..743f2abc8a7 100644
--- a/spec/workers/create_evidence_worker_spec.rb
+++ b/spec/workers/releases/create_evidence_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe CreateEvidenceWorker do
+RSpec.describe Releases::CreateEvidenceWorker do
let(:project) { create(:project, :repository) }
let(:release) { create(:release, project: project) }
let(:pipeline) { create(:ci_empty_pipeline, sha: release.sha, project: project) }
diff --git a/spec/workers/releases/manage_evidence_worker_spec.rb b/spec/workers/releases/manage_evidence_worker_spec.rb
new file mode 100644
index 00000000000..2fbfb6c9dc1
--- /dev/null
+++ b/spec/workers/releases/manage_evidence_worker_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Releases::ManageEvidenceWorker do
+ let(:project) { create(:project, :repository) }
+
+ shared_examples_for 'does not create a new Evidence record' do
+ specify :sidekiq_inline do
+ aggregate_failures do
+ expect(::Releases::CreateEvidenceService).not_to receive(:execute)
+ expect { described_class.new.perform }.to change(Releases::Evidence, :count).by(0)
+ end
+ end
+ end
+
+ context 'when `released_at` in inside the window' do
+ context 'when Evidence has not been created' do
+ let(:release) { create(:release, project: project, released_at: 1.hour.since) }
+
+ it 'creates a new Evidence record', :sidekiq_inline do
+ expect_next_instance_of(::Releases::CreateEvidenceService, release, { pipeline: nil }) do |service|
+ expect(service).to receive(:execute).and_call_original
+ end
+
+ expect { described_class.new.perform }.to change(Releases::Evidence, :count).by(1)
+ end
+ end
+
+ context 'when evidence has already been created' do
+ let(:release) { create(:release, project: project, released_at: 1.hour.since) }
+ let!(:evidence) { create(:evidence, release: release )}
+
+ it_behaves_like 'does not create a new Evidence record'
+ end
+ end
+
+ context 'when `released_at` is outside the window' do
+ let(:release) { create(:release, project: project, released_at: 300.minutes.since) }
+
+ it_behaves_like 'does not create a new Evidence record'
+ end
+end
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 4a80f4f9da6..82d975cb85a 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -58,6 +58,7 @@ RSpec.describe RepositoryImportWorker do
subject.perform(project.id)
end.to raise_error(RuntimeError, error)
expect(import_state.reload.jid).not_to be_nil
+ expect(import_state.status).to eq('failed')
end
it 'updates the error on Import/Export' do
@@ -74,6 +75,7 @@ RSpec.describe RepositoryImportWorker do
end.to raise_error(RuntimeError, error)
expect(import_state.reload.last_error).not_to be_nil
+ expect(import_state.status).to eq('failed')
end
end
diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb
index 7d66131f34e..758f7f75e03 100644
--- a/spec/workers/repository_remove_remote_worker_spec.rb
+++ b/spec/workers/repository_remove_remote_worker_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe RepositoryRemoveRemoteWorker do
expect(subject)
.to receive(:log_error)
- .with("Cannot obtain an exclusive lease for #{subject.class.name}. There must be another instance already in execution.")
+ .with("Cannot obtain an exclusive lease for #{lease_key}. There must be another instance already in execution.")
subject.perform(project.id, remote_name)
end
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index 858f5226c48..ef6a8d76d2c 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -3,9 +3,8 @@
require 'spec_helper'
RSpec.describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
- subject { described_class.new }
+ let_it_be(:remote_mirror) { create(:remote_mirror) }
- let(:remote_mirror) { create(:remote_mirror) }
let(:scheduled_time) { Time.current - 5.minutes }
around do |example|
@@ -19,6 +18,8 @@ RSpec.describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_st
end
describe '#perform' do
+ subject { described_class.new }
+
it 'calls out to the service to perform the update' do
expect_mirror_service_to_return(remote_mirror, status: :success)
@@ -68,4 +69,8 @@ RSpec.describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_st
subject.perform(remote_mirror.id, scheduled_time)
end
end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [remote_mirror.id, scheduled_time] }
+ end
end